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
1 change: 0 additions & 1 deletion .github/workflows/claude-code-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,3 @@ jobs:
prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}'
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
# or https://code.claude.com/docs/en/cli-reference for available options

1 change: 0 additions & 1 deletion .github/workflows/claude.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,3 @@ jobs:
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
# or https://code.claude.com/docs/en/cli-reference for available options
# claude_args: '--allowed-tools Bash(gh pr:*)'

60 changes: 60 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
minimum_prek_version: "0.4.14"
default_install_hook_types: [pre-commit]
default_stages: [pre-commit]

repos:
- repo: builtin
hooks:
- id: trailing-whitespace
stages: [pre-commit]
args: [--markdown-linebreak-ext=md]
- id: end-of-file-fixer
stages: [pre-commit]
- id: fix-byte-order-marker
stages: [pre-commit]
- id: mixed-line-ending
stages: [pre-commit]
- id: check-added-large-files
stages: [pre-commit]
- id: check-case-conflict
stages: [pre-commit]
- id: check-illegal-windows-names
stages: [pre-commit]
- id: check-json
stages: [pre-commit]
exclude: ^template/
- id: check-toml
stages: [pre-commit]
exclude: ^template/
- id: check-yaml
stages: [pre-commit]
exclude: ^template/
- id: check-merge-conflict
stages: [pre-commit]
- id: check-symlinks
stages: [pre-commit]
- id: destroyed-symlinks
stages: [pre-commit]
- id: detect-private-key
stages: [pre-commit]
- id: check-executables-have-shebangs
stages: [pre-commit]
- id: check-shebang-scripts-are-executable
stages: [pre-commit]

- repo: https://github.com/astral-sh/uv-pre-commit
rev: 0.12.0
hooks:
- id: uv-lock
stages: [pre-commit]

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.16.3
hooks:
- id: ruff-check
stages: [pre-commit]
args: [--fix]
exclude: ^template/
- id: ruff-format
stages: [pre-commit]
exclude: ^template/
2 changes: 2 additions & 0 deletions .prekignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# The nested config belongs to generated projects, not this prek workspace.
template/
4 changes: 3 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ This is a copier template for Python projects. It generates new Python projects

## Development

- Run `make bootstrap` once to sync dependencies and install the commit-only prek hook
- Run `make test` to verify the template generates correctly
- Run `make prek` to execute all prek checks against tracked files
- The test creates a temporary project using copier with default values and checks basic structure

## How the template works
Expand All @@ -25,4 +27,4 @@ This is a copier template for Python projects. It generates new Python projects

1. Edit files in `template/` to change what gets generated
2. Edit `copier.yml` to add/modify template questions
3. Run `make test` to verify changes work
3. Run `make test` and `make prek` to verify changes work
9 changes: 8 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
.PHONY: test
.PHONY: bootstrap test prek

bootstrap:
uv sync --dev
uv run prek install --prepare-hooks

test:
uv run pytest tests/ -v

prek:
uv run prek run --all-files
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ A minimal [copier](https://copier.readthedocs.io/) template for Python projects
- [uv](https://docs.astral.sh/uv/) for dependency management
- [pytest](https://docs.pytest.org/) for testing
- [ruff](https://docs.astral.sh/ruff/) for linting and formatting
- [pyright](https://github.com/microsoft/pyright) for type checking
- [ty](https://docs.astral.sh/ty/) for type checking
- [prek](https://prek.j178.dev/) for commit-time checks
- Optional [Typer](https://typer.tiangolo.com/) CLI
- GitHub Actions CI/CD workflows
- MIT license
Expand Down Expand Up @@ -41,8 +42,20 @@ copier copy gh:jooh/copier-minimal-python my-project

## Development

Set up the development environment and install the commit-only Git hook once:

```bash
make bootstrap
```

Run the template tests and, when needed, every prek check against all tracked files:

```bash
make test
make prek
```

The installed hook runs automatically on `git commit`. Safe formatting fixes must be reviewed and
staged before retrying the commit; no pre-push or other Git hooks are installed.

See [AGENTS.md](AGENTS.md) for more details on the template structure.
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ requires-python = ">=3.12"
dev = [
"copier>=9.0.0",
"jinja2-time>=0.2.0",
"prek>=0.4.14",
"pytest>=8.0.0",
]
8 changes: 4 additions & 4 deletions template/.github/workflows/cd_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
import pathlib
import re
import tomllib
import tomli_w
from dataclasses import dataclass

import tomli_w

# Strict semantic versioning pattern: MAJOR.MINOR.PATCH
# - MAJOR is either 0 (pre-1.0 semantics) or a non-zero integer without leading zeros.
Expand All @@ -24,7 +24,7 @@ class Version:
patch: int

@classmethod
def parse(cls, value: str) -> "Version | None":
def parse(cls, value: str) -> Version | None:
match = VERSION_PATTERN.match(value)
if not match:
return None
Expand All @@ -34,7 +34,7 @@ def parse(cls, value: str) -> "Version | None":
patch=int(match.group("patch")),
)

def bump_patch(self) -> "Version":
def bump_patch(self) -> Version:
return Version(self.major, self.minor, self.patch + 1)

def __str__(self) -> str:
Expand All @@ -54,7 +54,7 @@ def write_version(pyproject_path: pathlib.Path, version: Version) -> None:
data = tomllib.loads(pyproject_path.read_text())
project = data.get("project")
if not isinstance(project, dict):
raise ValueError(f"Missing [project] table in {pyproject_path}")
raise TypeError(f"Missing [project] table in {pyproject_path}")
if "version" not in project:
raise ValueError(f"Missing project.version in {pyproject_path}")
project["version"] = str(version)
Expand Down
55 changes: 55 additions & 0 deletions template/.pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
minimum_prek_version: "0.4.14"
default_install_hook_types: [pre-commit]
default_stages: [pre-commit]

repos:
- repo: builtin
hooks:
- id: trailing-whitespace
stages: [pre-commit]
args: [--markdown-linebreak-ext=md]
- id: end-of-file-fixer
stages: [pre-commit]
- id: fix-byte-order-marker
stages: [pre-commit]
- id: mixed-line-ending
stages: [pre-commit]
- id: check-added-large-files
stages: [pre-commit]
- id: check-case-conflict
stages: [pre-commit]
- id: check-illegal-windows-names
stages: [pre-commit]
- id: check-json
stages: [pre-commit]
- id: check-toml
stages: [pre-commit]
- id: check-yaml
stages: [pre-commit]
- id: check-merge-conflict
stages: [pre-commit]
- id: check-symlinks
stages: [pre-commit]
- id: destroyed-symlinks
stages: [pre-commit]
- id: detect-private-key
stages: [pre-commit]
- id: check-executables-have-shebangs
stages: [pre-commit]
- id: check-shebang-scripts-are-executable
stages: [pre-commit]

- repo: https://github.com/astral-sh/uv-pre-commit
rev: 0.12.0
hooks:
- id: uv-lock
stages: [pre-commit]

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.16.3
hooks:
- id: ruff-check
stages: [pre-commit]
args: [--fix]
- id: ruff-format
stages: [pre-commit]
3 changes: 3 additions & 0 deletions template/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,19 @@
- Requires Python {{ min_python_version }}+ (see `pyproject.toml`).
- Environment management is with uv.
- Run Python and related CLI tools via `uv run` so they use the uv virtualenv.
- Run `make bootstrap` once after cloning or initializing Git to sync dependencies and install the commit-only prek hook.

## Common commands
- Run tests: `make test`
- Run type checks: `make typecheck`
- Run ruff checks: `make ruff`
- Run all prek hooks against tracked files: `make prek`
- Run all checks: `make all-tests`

## Style and conventions
- TDD for all code development - write test, then run to verify it fails, then develop, then verify the test passes.
- All tasks should end by running `make all-tests` and verifying it passes.
- Run `make prek` before committing; safe automatic fixes must be reviewed and staged.
- Prefer updating or adding pytest tests in `tests/` for behavior changes.
{%- if has_cli %}
- For CLI changes, update both `src/{{ package_name }}/cli.py` and any relevant tests.
Expand Down
3 changes: 3 additions & 0 deletions template/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,19 @@
- Requires Python {{ min_python_version }}+ (see `pyproject.toml`).
- Environment management is with uv.
- Run Python and related CLI tools via `uv run` so they use the uv virtualenv.
- Run `make bootstrap` once after cloning or initializing Git to sync dependencies and install the commit-only prek hook.

## Common commands
- Run tests: `make test`
- Run type checks: `make typecheck`
- Run ruff checks: `make ruff`
- Run all prek hooks against tracked files: `make prek`
- Run all checks: `make all-tests`

## Style and conventions
- TDD for all code development - write test, then run to verify it fails, then develop, then verify the test passes.
- All tasks should end by running `make all-tests` and verifying it passes.
- Run `make prek` before committing; safe automatic fixes must be reviewed and staged.
- Prefer updating or adding pytest tests in `tests/` for behavior changes.
{%- if has_cli %}
- For CLI changes, update both `src/{{ package_name }}/cli.py` and any relevant tests.
Expand Down
9 changes: 8 additions & 1 deletion template/Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
.PHONY: test typecheck ruff all-tests
.PHONY: bootstrap test typecheck ruff all-tests prek

bootstrap:
uv sync --dev
uv run prek install --prepare-hooks

test:
uv run pytest --cov={{ package_name }} --cov-report=term-missing --cov-report=xml
Expand All @@ -10,3 +14,6 @@ ruff:
uv run ruff check .

all-tests: test typecheck ruff

prek:
uv run prek run --all-files
10 changes: 10 additions & 0 deletions template/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,20 @@ import {{ package_name }}

## Development

After cloning or initializing the Git repository, set up the environment and commit hook:

```bash
make bootstrap
```

The hook runs automatically only on `git commit`. Safe fixes must be reviewed and staged before
retrying the commit. Run every configured hook against all tracked files manually with `make prek`.

```bash
make test
make typecheck
make ruff
make prek
```

## Release process
Expand Down
3 changes: 2 additions & 1 deletion template/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,10 @@ exclude = ["uv.lock"]

[dependency-groups]
dev = [
"prek>=0.4.14",
"pytest>=9.0.2",
"pytest-cov>=6.0.0",
"ruff>=0.9.10",
"ruff>=0.16.3",
"tomli-w>=1.0.0",
"ty>=0.0.14",
]
2 changes: 1 addition & 1 deletion template/tests/test_cd_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import importlib.util
import sys
from typing import Protocol, cast
from pathlib import Path
from typing import Protocol, cast


class CdVersionModule(Protocol):
Expand Down
8 changes: 5 additions & 3 deletions template/tests/test_{{ package_name }}.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
"""Placeholder tests for {{ package_name }}."""

import {{ package_name }}
{% if has_cli %}
{% if has_cli -%}
from typer.testing import CliRunner

{% endif -%}
import {{ package_name }}
{% if has_cli -%}
from {{ package_name }}.cli import app

runner = CliRunner()
Expand Down Expand Up @@ -33,4 +35,4 @@ def test_cli_hello_with_name() -> None:
result = runner.invoke(app, ["--name", "World"])
assert result.exit_code == 0
assert "Hello, World!" in result.stdout
{% endif %}
{% endif -%}
Loading
Loading