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
29 changes: 29 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,35 @@ jobs:
make test-unit
uv build

# A rule that is selected and then ignored looks exactly like a rule that
# works: the gate above stays green either way. Hand the generated project
# the mistakes the selected rules exist to catch and make it say so, and an
# assert in tests/ that has to stay accepted.
- name: The generated config runs the rules it selects
working-directory: ${{ runner.temp }}/demo-lib
run: |
cat > demo_lib/probe.py <<'PY'
import datetime


def stamp(offset):
return datetime.datetime.now() + offset
PY
cat > tests/unit/test_probe.py <<'PY'
def test__assert__still_reads_as_a_test() -> None:
assert True
PY
report=$(uv run ruff check --output-format concise demo_lib/probe.py || true)
echo "$report"
for rule in DTZ005 ANN201 ANN001; do
case "$report" in
*"$rule"*) ;;
*) echo "::error::$rule never fired -- the config selects the rule and then ignores it"; exit 1 ;;
esac
done
uv run ruff check tests/unit/test_probe.py
rm demo_lib/probe.py tests/unit/test_probe.py

all-checks-passed:
name: All checks passed
if: always()
Expand Down
2 changes: 2 additions & 0 deletions template/CONTRIBUTING.md.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ make test # full suite with 90% coverage threshold
- **Docstrings** on public API only — Google style
- **Line length** — 120 characters (ruff enforced)
- **Quotes** — double quotes (ruff enforced)
- **Timezone-aware datetimes** — `datetime.now(tz=...)`, never `utcnow()` (ruff `DTZ`)
- **`assert` in tests only** — library code raises instead (ruff `S101`)
- **No comments** unless the *why* is non-obvious

## Commit messages
Expand Down
12 changes: 11 additions & 1 deletion template/pyproject.toml.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,18 @@ target-version = "py{{ python_min_version | replace('.', '') }}"
extend-exclude = ["*.md"]

[tool.ruff.lint]
# Everything selected here runs. An entry below names one rule the house style
# disagrees with -- never a whole family that `select` has just asked for, which
# reads as a promise the linter does not keep.
select = ["F", "E", "W", "I", "B", "N", "S", "C4", "DTZ", "SIM", "TRY", "PERF", "RUF", "UP", "ANN", "T20", "PTH", "PLC", "PLE", "PLW"]
ignore = ["TRY003", "S101", "ANN401", "RUF012", "S104", "S105", "S106", "ANN204", "DTZ", "ANN", "N802", "PERF401", "SIM105", "S607"]
ignore = ["TRY003", "ANN401", "RUF012", "S104", "ANN204", "N802", "PERF401", "SIM105", "S607"]

# Tests are held to a different standard from the library they exercise:
# `assert` is what a test is made of, and a credential in a fixture is a literal
# rather than a leak. Annotations are not on this list -- mypy reads
# {{ package_name }}/ and never tests/, so ruff is the only thing holding that line here.
[tool.ruff.lint.per-file-ignores]
"tests/**/*.py" = ["S101", "S105", "S106"]

[tool.ruff.format]
quote-style = "double"
Expand Down
Loading