Skip to content

Commit 1da67f1

Browse files
committed
chore: modernize packaging, linting, and CI quality gates
- Switch build backend from hatchling to uv_build (module-name pinned). - Run Ruff as a local uv-run hook (single version source in the lockfile), bump to 0.16.0, and expand the lint rule set (security, docstrings, annotations, pytest, pathlib, and more). - Add a uv-based CI matrix testing the supported Python range and dependency resolution bounds (lowest-direct on the minimum, highest on the maximum). - Add Dependabot for the uv and GitHub Actions ecosystems. - Document switching the type-check gate from Pyright to basedpyright.
1 parent 04071d2 commit 1da67f1

10 files changed

Lines changed: 172 additions & 49 deletions

File tree

.github/dependabot.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: "uv"
4+
directory: "/"
5+
schedule:
6+
interval: "weekly"
7+
groups:
8+
dependencies:
9+
patterns:
10+
- "*"
11+
- package-ecosystem: "github-actions"
12+
directory: "/"
13+
schedule:
14+
interval: "weekly"
15+
groups:
16+
actions:
17+
patterns:
18+
- "*"

.github/workflows/ci.yml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,44 @@ jobs:
4141

4242
- name: Run pre-push checks
4343
run: uv run --no-sync pre-commit run --hook-stage pre-push --all-files
44+
45+
tests-matrix:
46+
name: tests (py${{ matrix.python }}, ${{ matrix.resolution }})
47+
runs-on: ubuntu-latest
48+
timeout-minutes: 15
49+
strategy:
50+
fail-fast: false
51+
matrix:
52+
# Exercise the supported range at both ends: minimum Python with the
53+
# oldest direct dependencies, and maximum Python with the newest. This
54+
# leg is deliberately non-frozen (it re-resolves per matrix entry), so
55+
# it cannot reuse the --frozen pytest-coverage hook. Bump "3.14" when a
56+
# newer interpreter enters the supported range.
57+
include:
58+
- python: "3.12"
59+
resolution: "lowest-direct"
60+
- python: "3.14"
61+
resolution: "highest"
62+
steps:
63+
- name: Checkout
64+
uses: actions/checkout@v7
65+
66+
- name: Install uv
67+
uses: astral-sh/setup-uv@v8.2.0
68+
with:
69+
enable-cache: true
70+
71+
- name: Install Python
72+
run: uv python install ${{ matrix.python }}
73+
74+
- name: Sync environment (${{ matrix.resolution }})
75+
run: uv sync --python ${{ matrix.python }} --resolution ${{ matrix.resolution }}
76+
77+
- name: Run tests with branch coverage
78+
# The 100% threshold comes from [tool.coverage.report] in pyproject.toml,
79+
# the same source the pytest-coverage hook uses -- not a duplicated flag.
80+
run: >-
81+
uv run --no-sync python -m pytest
82+
--cov=fgcz_reference_project
83+
--cov-branch
84+
--cov-report=term-missing

.pre-commit-config.yaml

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
11
default_install_hook_types: [pre-commit, pre-push]
22

33
repos:
4-
- repo: https://github.com/astral-sh/ruff-pre-commit
5-
rev: v0.15.11
4+
- repo: local
65
hooks:
7-
- id: ruff
8-
args: [--fix]
6+
- id: ruff-check
7+
name: ruff (lint)
8+
entry: uv run --frozen ruff check --force-exclude --fix
9+
language: system
10+
types_or: [python, pyi]
11+
require_serial: true
912
stages: [pre-commit]
13+
1014
- id: ruff-format
15+
name: ruff (format)
16+
entry: uv run --frozen ruff format --force-exclude
17+
language: system
18+
types_or: [python, pyi]
19+
require_serial: true
1120
stages: [pre-commit]
1221

13-
- repo: local
14-
hooks:
1522
- id: pyright
1623
name: pyright (strict)
1724
entry: uv run --frozen --group dev pyright

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,22 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
44

55
## [Unreleased]
66

7+
### Added
8+
9+
- CI matrix that runs the tests across the supported Python range and
10+
dependency-resolution bounds (`lowest-direct` on the minimum interpreter,
11+
`highest` on the maximum).
12+
- Dependabot configuration for the `uv` and GitHub Actions ecosystems.
13+
- Documented how to switch the type-check gate from Pyright to basedpyright.
14+
715
### Changed
816

917
- Documented the reference project's intent and complete quality-gate stack.
1018
- Enabled blocking Ruff complexity and function-design checks.
19+
- Switched the build backend from hatchling to `uv_build`.
20+
- Run Ruff as a local `uv run` hook so its version lives only in the lockfile;
21+
upgraded Ruff to 0.16.0 and expanded the lint rule set (security, docstrings,
22+
annotations, pytest, pathlib, and more).
1123

1224
## [0.1.0] - 2026-07-23
1325

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ structure and quality gates.
1515

1616
| Area | Included |
1717
|---|---|
18-
| Packaging | `src/` layout, `pyproject.toml`, hatchling, uv lockfile, typed-package marker, console entry point |
18+
| Packaging | `src/` layout, `pyproject.toml`, uv build backend, uv lockfile, typed-package marker, console entry point |
1919
| Example | One typed library function and a small `argparse` CLI |
2020
| Tests | Unit and CLI error-path tests with 100% line and branch coverage |
2121
| Documentation | MkDocs Material site built with `--strict` and deployable to GitHub Pages |

docs/development.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,28 @@ uv run pre-commit run dependency-audit --hook-stage manual --all-files
2828
GitHub Actions runs the same audit weekly. Add project-specific checks as
2929
separate hooks, then invoke those hooks from CI so local and remote behavior
3030
remain aligned.
31+
32+
## Type checking
33+
34+
Strict [Pyright](https://github.com/microsoft/pyright) runs as a blocking
35+
pre-commit hook. To use [basedpyright](https://docs.basedpyright.com/) instead
36+
— a stricter, pure-Python fork that installs through uv without a separate
37+
Node runtime — make three changes:
38+
39+
- replace `pyright` with `basedpyright` in the `dev` group of `pyproject.toml`;
40+
- change the `pyright` hook `entry` in `.pre-commit-config.yaml` to
41+
`uv run --frozen basedpyright`;
42+
- configure it under `[tool.basedpyright]` (it reads the existing
43+
`[tool.pyright]` keys).
44+
45+
Pyright is the default because Microsoft maintains it; basedpyright adds
46+
strictness and richer reporting at the cost of a single-maintainer fork.
47+
48+
## Python and dependency ranges
49+
50+
The frozen pre-commit and pre-push stages run on a single pinned interpreter for
51+
a fast local loop. CI additionally runs the test suite across the supported
52+
range: the minimum Python with the oldest direct dependencies
53+
(`--resolution lowest-direct`) and the maximum Python with the newest
54+
(`--resolution highest`). This catches both under-specified lower bounds and
55+
breakage on new interpreters.

pyproject.toml

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ dependencies = []
1414
fgcz-reference = "fgcz_reference_project.cli:main"
1515

1616
[build-system]
17-
requires = ["hatchling>=1.28,<2"]
18-
build-backend = "hatchling.build"
17+
requires = ["uv_build>=0.11.7,<0.12"]
18+
build-backend = "uv_build"
1919

2020
[dependency-groups]
2121
dev = [
@@ -26,14 +26,14 @@ dev = [
2626
"pyright>=1.1.408,<2",
2727
"pytest>=9,<10",
2828
"pytest-cov>=7,<8",
29-
"ruff==0.15.11",
29+
"ruff==0.16.0",
3030
]
3131
docs = [
3232
"mkdocs-material>=9.7,<10",
3333
]
3434

35-
[tool.hatch.build.targets.wheel]
36-
packages = ["src/fgcz_reference_project"]
35+
[tool.uv.build-backend]
36+
module-name = "fgcz_reference_project"
3737

3838
[tool.pytest.ini_options]
3939
addopts = "-ra --strict-config --strict-markers"
@@ -45,27 +45,45 @@ target-version = "py312"
4545

4646
[tool.ruff.lint]
4747
select = [
48-
"A",
49-
"B",
50-
"C4",
51-
"C901",
52-
"E",
53-
"F",
54-
"I",
55-
"PLR0911",
56-
"PLR0912",
57-
"PLR0913",
58-
"PLR0915",
59-
"UP",
60-
"W",
48+
"F", # pyflakes
49+
"E", # pycodestyle errors
50+
"W", # pycodestyle warnings
51+
"I", # isort
52+
"N", # pep8-naming
53+
"UP", # pyupgrade
54+
"B", # flake8-bugbear
55+
"A", # flake8-builtins
56+
"C4", # flake8-comprehensions
57+
"RUF", # ruff-specific
58+
"C90", # mccabe complexity
59+
"PL", # pylint (convention/error/warning/refactor)
60+
"SIM", # flake8-simplify
61+
"RET", # flake8-return
62+
"PTH", # flake8-use-pathlib
63+
"PT", # flake8-pytest-style
64+
"TID", # flake8-tidy-imports
65+
"TC", # flake8-type-checking
66+
"ARG", # flake8-unused-arguments
67+
"EM", # flake8-errmsg
68+
"D", # pydocstyle
69+
"ANN", # flake8-annotations
70+
"S", # flake8-bandit (security)
6171
]
72+
ignore = ["ANN401"]
6273

6374
[tool.ruff.lint.mccabe]
6475
max-complexity = 10
6576

6677
[tool.ruff.lint.pylint]
6778
max-args = 7
6879

80+
[tool.ruff.lint.pydocstyle]
81+
convention = "google"
82+
83+
[tool.ruff.lint.per-file-ignores]
84+
"tests/**" = ["S101", "D", "ANN", "PLR2004", "ARG"]
85+
"scripts/**" = ["S603", "S607", "D", "PLR2004", "EM"]
86+
6987
[tool.ruff.format]
7088
quote-style = "double"
7189

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
1+
# Intentionally empty package marker; import from concrete modules (see AGENTS.md).
2+
# ruff: noqa: D104

src/fgcz_reference_project/naming.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,6 @@ def project_slug(name: str) -> str:
2121
"""
2222
slug = _NON_ALPHANUMERIC.sub("-", name.strip().lower()).strip("-")
2323
if not slug:
24-
raise ValueError("Project name must contain at least one letter or digit.")
24+
message = "Project name must contain at least one letter or digit."
25+
raise ValueError(message)
2526
return slug

uv.lock

Lines changed: 22 additions & 22 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)