Skip to content

Commit 7f0cc7e

Browse files
gaborbernatlayday
authored andcommitted
🔧 build(type): replace mypy with pyrefly (#1168)
Co-authored-by: layday <layday@protonmail.com>
1 parent 7ac9efe commit 7f0cc7e

15 files changed

Lines changed: 91 additions & 72 deletions
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Use ``pyrefly`` for project type checking and require 100% package type coverage - by :user:`gaborbernat`.

‎docs/development/contributing.rst‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,8 @@ build follows modern Python code style conventions enforced by `ruff <https://do
8989
a pull request, run the linter to ensure your code meets the project's style guidelines. The ruff configuration is
9090
defined in the pyproject.toml file and includes both formatting and linting rules.
9191

92-
The project also uses type annotations throughout the codebase. All new code should include appropriate type hints, and
93-
changes to existing code should preserve or improve type annotations. Use `pyright
94-
<https://microsoft.github.io/pyright/>`_ for type checking to verify your type annotations are correct.
92+
The project uses type annotations throughout the codebase. Add type hints to new code and preserve or improve existing
93+
annotations. The ``type`` tox environment runs `Pyrefly <https://pyrefly.org/>`_ and requires full package coverage.
9594

9695
***************
9796
Documentation

‎pyproject.toml‎

Lines changed: 17 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ test = [
108108
{ include-group = "extra" },
109109
]
110110
typing = [
111+
"colorama",
111112
"importlib-metadata >= 5.1",
112113
"tomli",
113114
"typing-extensions >= 4.0.0",
@@ -116,8 +117,8 @@ typing = [
116117
{ include-group = "test-core" },
117118
{ include-group = "extra" },
118119
]
119-
mypy = [
120-
"mypy ~= 2.3.0",
120+
pyrefly = [
121+
"pyrefly >= 1.2",
121122
{ include-group = "typing" },
122123
]
123124
release = [
@@ -130,7 +131,7 @@ release = [
130131
dev = [
131132
"flit-core",
132133
{ include-group = "test" },
133-
{ include-group = "mypy" },
134+
{ include-group = "pyrefly" },
134135
]
135136

136137
[tool.flit.sdist]
@@ -191,38 +192,19 @@ filterwarnings = [
191192
"ignore:os.path.commonprefix:DeprecationWarning" # https://github.com/pypa/pyproject-hooks/pull/222
192193
]
193194

194-
[tool.mypy]
195-
files = ["src", "tests", "tasks", "docs"]
196-
exclude = ["tests/packages"]
197-
python_version = "3.10"
198-
native_parser = true
199-
strict = true
200-
disallow_any_explicit = true
201-
disallow_any_decorated = true
202-
disallow_any_unimported = true
203-
disallow_untyped_globals = true
204-
disallow_redefinition = true
205-
warn_unused_configs = true
206-
warn_unreachable = true
207-
enable_error_code = [
208-
"deprecated",
209-
"exhaustive-match",
210-
"ignore-without-code",
211-
"mutable-override",
212-
"possibly-undefined",
213-
"redundant-expr",
214-
"redundant-self",
215-
"truthy-bool",
216-
"truthy-iterable",
217-
"unimported-reveal",
218-
"unused-awaitable",
219-
]
220-
221-
[[tool.mypy.overrides]]
222-
module = [
223-
"virtualenv", # Optional dependency
224-
]
225-
ignore_missing_imports = true
195+
[tool.pyrefly]
196+
project-includes = ["src", "tests", "tasks", "docs"]
197+
project-excludes = ["**/tests/packages*"]
198+
search-path = ["src", "tests"]
199+
python-version = "3.10"
200+
preset = "all"
201+
202+
[tool.pyrefly.errors]
203+
# `@override` needs typing_extensions before 3.12; build takes no runtime deps for typing
204+
missing-override-decorator = false
205+
# These style rules reject intentional truthiness checks and side-effect calls throughout the project.
206+
implicit-bool = false
207+
unused-call-result = false
226208

227209
[tool.ruff]
228210
exclude = [

‎src/build/_builder.py‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -269,10 +269,15 @@ def get_requires_for_build(
269269
"""
270270
_ctx.log(f'Getting build dependencies for {distribution}...', kind=('step',))
271271
hook_name = f'get_requires_for_build_{distribution}'
272-
get_requires = getattr(self._hook, hook_name)
273272

274273
with self._handle_backend(hook_name):
275-
return set(get_requires(config_settings))
274+
if distribution == 'editable':
275+
requires = self._hook.get_requires_for_build_editable(config_settings)
276+
elif distribution == 'sdist':
277+
requires = self._hook.get_requires_for_build_sdist(config_settings)
278+
else:
279+
requires = self._hook.get_requires_for_build_wheel(config_settings)
280+
return set(requires)
276281

277282
def check_dependencies(
278283
self,

‎src/build/_ctx.py‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,16 @@ class Logger(typing.Protocol): # pragma: no cover
2121
def __call__(self, message: str, *, kind: tuple[str, ...] | None = None) -> None: ...
2222

2323

24-
_package_name = __spec__.parent
25-
_default_logger = logging.getLogger(_package_name)
24+
_default_logger = logging.getLogger(typing.cast('str', __spec__.parent))
2625

2726

2827
def _log_default(message: str, *, kind: tuple[str, ...] | None = None) -> None: # noqa: ARG001
2928
# the log function that works in tests, real log function is set in __main__
3029
_default_logger.log(logging.INFO, message, stacklevel=2)
3130

3231

33-
LOGGER = contextvars.ContextVar('LOGGER', default=_log_default)
34-
VERBOSITY = contextvars.ContextVar('VERBOSITY', default=0)
32+
LOGGER = contextvars.ContextVar[Logger]('LOGGER', default=_log_default)
33+
VERBOSITY = contextvars.ContextVar[int]('VERBOSITY', default=0)
3534

3635

3736
def log_subprocess_error(error: subprocess.CalledProcessError) -> None:

‎src/build/_exceptions.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def __init__(
2424
description: str | None = None,
2525
) -> None:
2626
super().__init__()
27-
self.exception = exception
27+
self.exception: Exception = exception
2828
self._description = description
2929

3030
def __str__(self) -> str:
@@ -49,7 +49,7 @@ class FailedProcessError(Exception):
4949

5050
def __init__(self, exception: subprocess.CalledProcessError, description: str) -> None:
5151
super().__init__()
52-
self.exception = exception
52+
self.exception: subprocess.CalledProcessError = exception
5353
self._description = description
5454

5555
def __str__(self) -> str:

‎src/build/_types.py‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@
1515

1616
# A decoded JSON value, as produced by ``json.load``. Uses the covariant ``Sequence``/``Mapping``
1717
# so concrete literals (e.g. ``str | list[str]``) are also assignable to it.
18-
JSONValue = str | int | float | bool | None | collections.abc.Sequence['JSONValue'] | collections.abc.Mapping[str, 'JSONValue']
18+
JSONValue: typing.TypeAlias = (
19+
str | int | float | bool | collections.abc.Sequence['JSONValue'] | collections.abc.Mapping[str, 'JSONValue'] | None
20+
)
1921

2022
# A value as produced by ``tomllib`` when parsing ``pyproject.toml``. Uses the covariant
2123
# ``Sequence``/``Mapping`` so concrete literals (e.g. ``dict[str, list[str]]``) are assignable.
22-
TOMLValue = (
24+
TOMLValue: typing.TypeAlias = (
2325
str
2426
| int
2527
| float

‎src/build/env.py‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ class _DistArgs(typing.TypedDict, total=False):
6262

6363
Installer = typing.Literal['pip', 'uv']
6464

65-
INSTALLERS = typing.get_args(Installer)
65+
INSTALLERS: tuple[Installer, ...] = typing.get_args(Installer)
6666

6767

6868
class IsolatedEnv(typing.Protocol):
@@ -107,6 +107,9 @@ class DefaultIsolatedEnv(IsolatedEnv):
107107
Isolated environment which supports several different underlying implementations.
108108
"""
109109

110+
_env_backend: _EnvBackend
111+
_path: str
112+
110113
def __init__(
111114
self,
112115
*,
@@ -142,8 +145,6 @@ def __enter__(self) -> Self:
142145
path = os.path.realpath(path)
143146
self._path = path
144147

145-
self._env_backend: _EnvBackend
146-
147148
# uv is opt-in only.
148149
if self.installer == 'uv':
149150
self._env_backend = _UvBackend()
@@ -437,6 +438,9 @@ def display_name(self) -> str:
437438

438439

439440
class _UvBackend(_EnvBackend):
441+
_env_path: str
442+
_uv_bin: str
443+
440444
def create(self, path: str) -> None:
441445
import venv
442446

‎tasks/release.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from pathlib import Path
66
from subprocess import call, check_call
7+
from typing import cast
78

89
from git import Commit, Remote, Repo, TagReference
910
from packaging.version import Version
@@ -39,7 +40,7 @@ def main(version_str: str, *, push: bool) -> None:
3940
def resolve_version(version_str: str, repo: Repo) -> Version:
4041
if version_str not in {'auto', 'major', 'minor', 'patch'}:
4142
return Version(version_str)
42-
parts = [int(x) for x in repo.git.describe('--tags', '--abbrev=0').lstrip('v').split('.')[:3]]
43+
parts = [int(part) for part in cast('str', repo.git.describe('--tags', '--abbrev=0')).lstrip('v').split('.')[:3]]
4344
match detect_bump() if version_str == 'auto' else version_str:
4445
case 'major':
4546
parts = [parts[0] + 1, 0, 0]

‎tests/conftest.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from collections.abc import Callable, Generator
1616
from functools import partial, update_wrapper
1717
from pathlib import Path
18-
from typing import Protocol
18+
from typing import Protocol, cast
1919

2020
import pytest
2121

@@ -53,7 +53,7 @@ def pytest_collection_modifyitems(config: pytest.Config, items: list[pytest.Item
5353
elif config.getoption('--only-integration'): # pragma: no cover
5454
item.add_marker(skip_other)
5555
# run integration tests after unit tests
56-
items.sort(key=lambda i: 1 if is_integration(i) else 0)
56+
items.sort(key=is_integration)
5757

5858

5959
def _xfail_isolated_strict(item: pytest.Item, *, is_integration_file: bool) -> bool:
@@ -123,7 +123,7 @@ def is_setuptools(package_path: Path) -> bool:
123123
pyproject = package_path / 'pyproject.toml'
124124
try:
125125
with pyproject.open('rb') as f:
126-
pp = tomllib.load(f)
126+
pp = cast(dict[str, dict[str, str]], tomllib.load(f))
127127
except (FileNotFoundError, ValueError):
128128
return True
129129
return 'setuptools' in pp.get('build-system', {}).get('build-backend', 'setuptools')

0 commit comments

Comments
 (0)