From f874ac0c9073680074c29dd670f99245e649a058 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Fri, 23 Oct 2026 15:14:43 -0400 Subject: [PATCH 1/4] feat: detect ty.toml/pyrefly.toml and rename MyPy family MY100 now passes with a standalone ty.toml or pyrefly.toml. The family shows as "Type checking", and the typing guide covers MyPy, Pyrefly, and ty. Assisted-by: ClaudeCode:claude-opus-5-5 --- README.md | 4 ++-- docs/guides/index.md | 4 ++-- docs/guides/style.md | 5 ++++- docs/guides/typing.md | 34 ++++++++++++++++++++++++------- src/sp_repo_review/checks/mypy.py | 20 +++++++++++------- src/sp_repo_review/families.py | 2 +- src/sp_repo_review/files.py | 2 ++ tests/test_mypy.py | 25 +++++++++++++++-------- 8 files changed, 68 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 18c2bc25..0317eccd 100644 --- a/README.md +++ b/README.md @@ -361,9 +361,9 @@ for family, grp in itertools.groupby(collected.checks.items(), key=lambda x: x[1 - [`GH211`](https://learn.scientific-python.org/development/guides/gha-basic#GH211): Do not pin core actions as major versions - [`GH212`](https://learn.scientific-python.org/development/guides/gha-basic#GH212): Require GHA update grouping -### MyPy +### Type checking -- [`MY100`](https://learn.scientific-python.org/development/guides/style#MY100): Uses a type checker (pyproject config) +- [`MY100`](https://learn.scientific-python.org/development/guides/style#MY100): Uses a type checker - [`MY101`](https://learn.scientific-python.org/development/guides/style#MY101): Type checker in strict mode - `MY102`: MyPy show_error_codes deprecated - [`MY103`](https://learn.scientific-python.org/development/guides/style#MY103): MyPy warn unreachable diff --git a/docs/guides/index.md b/docs/guides/index.md index 1a2727b9..89a5e420 100644 --- a/docs/guides/index.md +++ b/docs/guides/index.md @@ -9,7 +9,7 @@ for your `CONTRIBUTING.md`, and at least glance through other sections. Following that, there are recommendations for [style][], intended to promote good practices and to ensure continuity across the packages. There is a -[dedicated page for static type checking with MyPy][mypy]. There are then +[dedicated page for static type checking][typing]. There are then guides on [simple packaging][] and [compiled packaging][], which should help in ensuring a consistent developer and user experience when working with distribution. @@ -38,7 +38,7 @@ WebAssembly! All checks point to a linked badge in the guide. [tutorials]: /tutorials/index.md [style]: /guides/style.md -[mypy]: /guides/typing.md +[typing]: /guides/typing.md [docs]: /guides/docs.md [simple packaging]: /guides/packaging_simple.md [compiled packaging]: /guides/packaging_compiled.md diff --git a/docs/guides/style.md b/docs/guides/style.md index 9ff4d0f1..eabba299 100644 --- a/docs/guides/style.md +++ b/docs/guides/style.md @@ -690,7 +690,10 @@ another. ::: :::: -These checkers read their configuration from `pyproject.toml`: +These checkers read their configuration from `pyproject.toml`, shown below. +Pyrefly and ty can also use a standalone `pyrefly.toml` or `ty.toml` file. In +these files, drop the `tool.` prefix from the table names. {rr}`MY100` +detects these files, but the other checks only read `pyproject.toml`. ::::{tab-set} :::{tab-item} MyPy diff --git a/docs/guides/typing.md b/docs/guides/typing.md index ead3d94a..21241ddf 100644 --- a/docs/guides/typing.md +++ b/docs/guides/typing.md @@ -53,17 +53,37 @@ code also runs. ### Configuration -By default, the type checker does as little as possible, so that you can add it -iteratively to a code base. By default: +The three main type checkers are [MyPy](https://mypy.readthedocs.io), +[Pyrefly](https://pyrefly.org), and [ty](https://docs.astral.sh/ty/). They +follow the same typing specification, but their defaults are different. + +MyPy does as little as possible by default, so that you can add it gradually +to a code base: - All untyped variables and return values will be `Any`. - Code inside untyped functions is not checked _at all_. -You can add configuration to `pyproject.toml` (and a little bit to the files -themselves), or you can go all the way and pass `--strict`, which will turn on -everything. Try to turn on as much as possible, and increase it until you can -run with full `strict` checking. See the [style page][] for configuration -suggestions. +Pyrefly and ty check the bodies of all functions by default, including untyped +ones. They also infer return types of unannotated functions, so they can find +errors in code that has no annotations. + +Each checker reads configuration from `pyproject.toml`. Pyrefly and ty can +also use their own file (`pyrefly.toml` or `ty.toml`). Turn on as much +checking as you can, and increase it over time: + +- MyPy: set `strict = true` (or pass `--strict`). +- Pyrefly: set `preset = "strict"`. +- ty: there is no single strict switch. Set the severity of individual rules + in `[tool.ty.rules]`. + +All three understand `# type: ignore`. Pyrefly and ty also have their own +ignore comments (`# pyrefly: ignore` and `# ty: ignore`) that affect only that +checker. See the [style page][] for +configuration suggestions for each checker. + +The checkers do not always agree, because each one implements the typing +specification differently. If your library supports more than one checker, run +each one in CI. [style page]: /guides/style.md diff --git a/src/sp_repo_review/checks/mypy.py b/src/sp_repo_review/checks/mypy.py index 4edcf315..14045515 100644 --- a/src/sp_repo_review/checks/mypy.py +++ b/src/sp_repo_review/checks/mypy.py @@ -1,30 +1,36 @@ from __future__ import annotations -__lazy_modules__ = ["typing"] - -from typing import Any +from typing import TYPE_CHECKING, Any from . import mk_url +if TYPE_CHECKING: + from importlib.resources.abc import Traversable + +MY100_CONFIG_FILES = ("ty.toml", "pyrefly.toml") + class MyPy: family = "mypy" class MY100(MyPy): - "Uses a type checker (pyproject config)" + "Uses a type checker" requires = {"PY001"} url = mk_url("style") @staticmethod - def check(pyproject: dict[str, Any]) -> bool: + def check(pyproject: dict[str, Any], package: Traversable) -> bool: """ Must have a `tool.mypy`, `tool.pyrefly`, or `tool.ty` section in - `pyproject.toml`. Other forms of configuration are not supported by - this check. + `pyproject.toml`, or a `ty.toml` or `pyrefly.toml` file. The other checks in this family only read + `pyproject.toml`. """ + if any(package.joinpath(fn).is_file() for fn in MY100_CONFIG_FILES): + return True + match pyproject: case { "tool": {"mypy": object()} | {"pyrefly": object()} | {"ty": object()} diff --git a/src/sp_repo_review/families.py b/src/sp_repo_review/families.py index 2dbbf5c1..9e8011eb 100644 --- a/src/sp_repo_review/families.py +++ b/src/sp_repo_review/families.py @@ -122,7 +122,7 @@ def get_families( readme_note="Will not show up if using lefthook instead of pre-commit/prek.", ), "mypy": Family( - name="MyPy", + name="Type checking", ), "ruff": Family( name="Ruff", diff --git a/src/sp_repo_review/files.py b/src/sp_repo_review/files.py index fee824a5..a25f085f 100644 --- a/src/sp_repo_review/files.py +++ b/src/sp_repo_review/files.py @@ -26,6 +26,8 @@ def prefetch_package() -> set[str]: ".pre-commit-config.yaml", ".readthedocs.yml", "noxfile.py", + "ty.toml", + "pyrefly.toml", "ruff.toml", ".ruff.toml", } diff --git a/tests/test_mypy.py b/tests/test_mypy.py index 19994897..386c6c50 100644 --- a/tests/test_mypy.py +++ b/tests/test_mypy.py @@ -1,32 +1,35 @@ +from pathlib import Path + +import pytest from repo_review.testing import compute_check, toml_loads -def test_my100_present(): +def test_my100_present(tmp_path: Path): toml = toml_loads(""" [tool.mypy] strict = true """) - assert compute_check("MY100", pyproject=toml).result + assert compute_check("MY100", pyproject=toml, package=tmp_path).result -def test_my100_pyrefly(): +def test_my100_pyrefly(tmp_path: Path): toml = toml_loads(""" [tool.pyrefly] project-includes = ["src"] """) - assert compute_check("MY100", pyproject=toml).result + assert compute_check("MY100", pyproject=toml, package=tmp_path).result -def test_my100_ty(): +def test_my100_ty(tmp_path: Path): toml = toml_loads(""" [tool.ty.src] include = ["src"] """) - assert compute_check("MY100", pyproject=toml).result + assert compute_check("MY100", pyproject=toml, package=tmp_path).result -def test_my100_missing(): - assert not compute_check("MY100", pyproject={}).result +def test_my100_missing(tmp_path: Path): + assert not compute_check("MY100", pyproject={}, package=tmp_path).result def test_my101_present_bool(): @@ -156,3 +159,9 @@ def test_my106_missing(): enable_error_code = ["ignore-without-code", "redundant-expr"] """) assert not compute_check("MY106", pyproject=toml).result + + +@pytest.mark.parametrize("name", ["ty.toml", "pyrefly.toml"]) +def test_my100_config_file(tmp_path: Path, name: str): + tmp_path.joinpath(name).touch() + assert compute_check("MY100", pyproject={}, package=tmp_path).result From 1c26fc44a07c7bab9b9094703812ef9e273b4ded Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Fri, 23 Oct 2026 15:17:12 -0400 Subject: [PATCH 2/4] docs: use tabs for type checkers in typing guide Assisted-by: ClaudeCode:claude-opus-5-5 --- docs/guides/typing.md | 48 ++++++++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/docs/guides/typing.md b/docs/guides/typing.md index 21241ddf..662d6496 100644 --- a/docs/guides/typing.md +++ b/docs/guides/typing.md @@ -57,29 +57,43 @@ The three main type checkers are [MyPy](https://mypy.readthedocs.io), [Pyrefly](https://pyrefly.org), and [ty](https://docs.astral.sh/ty/). They follow the same typing specification, but their defaults are different. +Each checker reads configuration from `pyproject.toml`. Turn on as much +checking as you can, and increase it over time. See the [style page][] for +configuration suggestions for each checker. + +::::{tab-set} +:::{tab-item} MyPy +:sync: mypy MyPy does as little as possible by default, so that you can add it gradually to a code base: - All untyped variables and return values will be `Any`. - Code inside untyped functions is not checked _at all_. -Pyrefly and ty check the bodies of all functions by default, including untyped -ones. They also infer return types of unannotated functions, so they can find -errors in code that has no annotations. - -Each checker reads configuration from `pyproject.toml`. Pyrefly and ty can -also use their own file (`pyrefly.toml` or `ty.toml`). Turn on as much -checking as you can, and increase it over time: - -- MyPy: set `strict = true` (or pass `--strict`). -- Pyrefly: set `preset = "strict"`. -- ty: there is no single strict switch. Set the severity of individual rules - in `[tool.ty.rules]`. - -All three understand `# type: ignore`. Pyrefly and ty also have their own -ignore comments (`# pyrefly: ignore` and `# ty: ignore`) that affect only that -checker. See the [style page][] for -configuration suggestions for each checker. +Set `strict = true` (or pass `--strict`) to turn on all checks. Disable MyPy on +a line with `# type: ignore[error-code]`. +::: +:::{tab-item} Pyrefly +:sync: pyrefly +Pyrefly checks the bodies of all functions by default, including untyped ones. +It also infers return types of unannotated functions, so it can find errors in +code that has no annotations. + +Set `preset = "strict"` to turn on stricter checks. Configuration can also go +in a `pyrefly.toml` file. Disable Pyrefly on a line with `# pyrefly: ignore`; +`# type: ignore` is honored as well. +::: +:::{tab-item} ty +:sync: ty +ty checks the bodies of all functions by default, including untyped ones. It +also infers return types of unannotated functions, so it can find errors in +code that has no annotations. + +There is no single strict switch. Set the severity of individual rules in +`[tool.ty.rules]`. Configuration can also go in a `ty.toml` file. Disable ty on +a line with `# ty: ignore`; `# type: ignore` is honored as well. +::: +:::: The checkers do not always agree, because each one implements the typing specification differently. If your library supports more than one checker, run From c33f669eed43a9910c8e2171f216263c0de984ad Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Fri, 23 Oct 2026 15:23:18 -0400 Subject: [PATCH 3/4] docs: touch up mention of shared ignore Signed-off-by: Henry Schreiner --- docs/guides/typing.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/guides/typing.md b/docs/guides/typing.md index 662d6496..b9a40d42 100644 --- a/docs/guides/typing.md +++ b/docs/guides/typing.md @@ -80,8 +80,10 @@ It also infers return types of unannotated functions, so it can find errors in code that has no annotations. Set `preset = "strict"` to turn on stricter checks. Configuration can also go -in a `pyrefly.toml` file. Disable Pyrefly on a line with `# pyrefly: ignore`; -`# type: ignore` is honored as well. +in a `pyrefly.toml` file. Disable Pyrefly only on a line with `# pyrefly: +ignore`. Note that pyrefly will try to read mypy's config if no pyrefly config +is present. + ::: :::{tab-item} ty :sync: ty @@ -90,14 +92,14 @@ also infers return types of unannotated functions, so it can find errors in code that has no annotations. There is no single strict switch. Set the severity of individual rules in -`[tool.ty.rules]`. Configuration can also go in a `ty.toml` file. Disable ty on -a line with `# ty: ignore`; `# type: ignore` is honored as well. +`[tool.ty.rules]`. Configuration can also go in a `ty.toml` file. Disable ty +only on a line with `# ty: ignore`. ::: :::: The checkers do not always agree, because each one implements the typing specification differently. If your library supports more than one checker, run -each one in CI. +each one in CI. All three support `# type: ignore`. [style page]: /guides/style.md From ebbad347299cb9fd358e43020e1967c24cff9c90 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Fri, 23 Oct 2026 15:24:53 -0400 Subject: [PATCH 4/4] Apply suggestion from @henryiii --- docs/guides/style.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/guides/style.md b/docs/guides/style.md index eabba299..f1b452a0 100644 --- a/docs/guides/style.md +++ b/docs/guides/style.md @@ -692,8 +692,7 @@ another. These checkers read their configuration from `pyproject.toml`, shown below. Pyrefly and ty can also use a standalone `pyrefly.toml` or `ty.toml` file. In -these files, drop the `tool.` prefix from the table names. {rr}`MY100` -detects these files, but the other checks only read `pyproject.toml`. +these files, drop the `tool.` prefix from the table names. ::::{tab-set} :::{tab-item} MyPy