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
18 changes: 18 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
version: 2
updates:
- package-ecosystem: "uv"
directory: "/"
schedule:
interval: "weekly"
groups:
dependencies:
patterns:
- "*"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
groups:
actions:
patterns:
- "*"
41 changes: 41 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,44 @@ jobs:

- name: Run pre-push checks
run: uv run --no-sync pre-commit run --hook-stage pre-push --all-files

tests-matrix:
name: tests (py${{ matrix.python }}, ${{ matrix.resolution }})
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
# Exercise the supported range at both ends: minimum Python with the
# oldest direct dependencies, and maximum Python with the newest. This
# leg is deliberately non-frozen (it re-resolves per matrix entry), so
# it cannot reuse the --frozen pytest-coverage hook. Bump "3.14" when a
# newer interpreter enters the supported range.
include:
- python: "3.12"
resolution: "lowest-direct"
- python: "3.14"
resolution: "highest"
steps:
- name: Checkout
uses: actions/checkout@v7

- name: Install uv
uses: astral-sh/setup-uv@v8.2.0
with:
enable-cache: true

- name: Install Python
run: uv python install ${{ matrix.python }}

- name: Sync environment (${{ matrix.resolution }})
run: uv sync --python ${{ matrix.python }} --resolution ${{ matrix.resolution }}

- name: Run tests with branch coverage
# The 100% threshold comes from [tool.coverage.report] in pyproject.toml,
# the same source the pytest-coverage hook uses -- not a duplicated flag.
run: >-
uv run --no-sync python -m pytest
--cov=fgcz_reference_project
--cov-branch
--cov-report=term-missing
19 changes: 13 additions & 6 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
default_install_hook_types: [pre-commit, pre-push]

repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.11
- repo: local
hooks:
- id: ruff
args: [--fix]
- id: ruff-check
name: ruff (lint)
entry: uv run --frozen ruff check --force-exclude --fix
language: system
types_or: [python, pyi]
require_serial: true
stages: [pre-commit]

- id: ruff-format
name: ruff (format)
entry: uv run --frozen ruff format --force-exclude
language: system
types_or: [python, pyi]
require_serial: true
stages: [pre-commit]

- repo: local
hooks:
- id: pyright
name: pyright (strict)
entry: uv run --frozen --group dev pyright
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,22 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [Unreleased]

### Added

- CI matrix that runs the tests across the supported Python range and
dependency-resolution bounds (`lowest-direct` on the minimum interpreter,
`highest` on the maximum).
- Dependabot configuration for the `uv` and GitHub Actions ecosystems.
- Documented how to switch the type-check gate from Pyright to basedpyright.

### Changed

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

## [0.1.0] - 2026-07-23

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ structure and quality gates.

| Area | Included |
|---|---|
| Packaging | `src/` layout, `pyproject.toml`, hatchling, uv lockfile, typed-package marker, console entry point |
| Packaging | `src/` layout, `pyproject.toml`, uv build backend, uv lockfile, typed-package marker, console entry point |
| Example | One typed library function and a small `argparse` CLI |
| Tests | Unit and CLI error-path tests with 100% line and branch coverage |
| Documentation | MkDocs Material site built with `--strict` and deployable to GitHub Pages |
Expand Down
25 changes: 25 additions & 0 deletions docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,28 @@ uv run pre-commit run dependency-audit --hook-stage manual --all-files
GitHub Actions runs the same audit weekly. Add project-specific checks as
separate hooks, then invoke those hooks from CI so local and remote behavior
remain aligned.

## Type checking

Strict [Pyright](https://github.com/microsoft/pyright) runs as a blocking
pre-commit hook. To use [basedpyright](https://docs.basedpyright.com/) instead
— a stricter, pure-Python fork that installs through uv without a separate
Node runtime — make three changes:

- replace `pyright` with `basedpyright` in the `dev` group of `pyproject.toml`;
- change the `pyright` hook `entry` in `.pre-commit-config.yaml` to
`uv run --frozen basedpyright`;
- configure it under `[tool.basedpyright]` (it reads the existing
`[tool.pyright]` keys).

Pyright is the default because Microsoft maintains it; basedpyright adds
strictness and richer reporting at the cost of a single-maintainer fork.

## Python and dependency ranges

The frozen pre-commit and pre-push stages run on a single pinned interpreter for
a fast local loop. CI additionally runs the test suite across the supported
range: the minimum Python with the oldest direct dependencies
(`--resolution lowest-direct`) and the maximum Python with the newest
(`--resolution highest`). This catches both under-specified lower bounds and
breakage on new interpreters.
54 changes: 36 additions & 18 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ dependencies = []
fgcz-reference = "fgcz_reference_project.cli:main"

[build-system]
requires = ["hatchling>=1.28,<2"]
build-backend = "hatchling.build"
requires = ["uv_build>=0.11.7,<0.12"]
build-backend = "uv_build"

[dependency-groups]
dev = [
Expand All @@ -26,14 +26,14 @@ dev = [
"pyright>=1.1.408,<2",
"pytest>=9,<10",
"pytest-cov>=7,<8",
"ruff==0.15.11",
"ruff==0.16.0",
]
docs = [
"mkdocs-material>=9.7,<10",
]

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

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

[tool.ruff.lint]

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the choice of the ideal ruff rules is extremely subjective. Not even sure if I agree with this. What you can also do is start with the minimum, and then run

uv run ruff check --select ALL --statistics

to get a rough idea of the problems which might be present.

select = [
"A",
"B",
"C4",
"C901",
"E",
"F",
"I",
"PLR0911",
"PLR0912",
"PLR0913",
"PLR0915",
"UP",
"W",
"F", # pyflakes
"E", # pycodestyle errors
"W", # pycodestyle warnings
"I", # isort
"N", # pep8-naming
"UP", # pyupgrade
"B", # flake8-bugbear
"A", # flake8-builtins
"C4", # flake8-comprehensions
"RUF", # ruff-specific
"C90", # mccabe complexity
"PL", # pylint (convention/error/warning/refactor)
"SIM", # flake8-simplify
"RET", # flake8-return
"PTH", # flake8-use-pathlib
"PT", # flake8-pytest-style
"TID", # flake8-tidy-imports
"TC", # flake8-type-checking
"ARG", # flake8-unused-arguments
"EM", # flake8-errmsg
"D", # pydocstyle
"ANN", # flake8-annotations
"S", # flake8-bandit (security)
]
ignore = ["ANN401"]

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

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

[tool.ruff.lint.pydocstyle]
convention = "google"

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not using any of the supported conventions at the moment, since I still use ReST style docstrings...
Google style is more terse, than numpy style.

Type: "google" | "numpy" | "pep257"


[tool.ruff.lint.per-file-ignores]
"tests/**" = ["S101", "D", "ANN", "PLR2004", "ARG"]
"scripts/**" = ["S603", "S607", "D", "PLR2004", "EM"]

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

Expand Down
3 changes: 2 additions & 1 deletion src/fgcz_reference_project/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@

# Intentionally empty package marker; import from concrete modules (see AGENTS.md).
# ruff: noqa: D104
3 changes: 2 additions & 1 deletion src/fgcz_reference_project/naming.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@ def project_slug(name: str) -> str:
"""
slug = _NON_ALPHANUMERIC.sub("-", name.strip().lower()).strip("-")
if not slug:
raise ValueError("Project name must contain at least one letter or digit.")
message = "Project name must contain at least one letter or digit."
raise ValueError(message)
return slug
44 changes: 22 additions & 22 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading