chore: stricter mypy (#2053)

* chore: improve mypy

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>

* chore(types): type functions in tests

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>

* chore(types): No partial types in tests

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>

---------

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
This commit is contained in:
Henry Schreiner
2024-10-22 10:16:59 -04:00
committed by GitHub
parent b4d1e17765
commit b98602705f
27 changed files with 131 additions and 111 deletions
+2
View File
@@ -32,6 +32,7 @@ repos:
- nox
- packaging
- pygithub
- pytest
- rich
- tomli
- tomli_w
@@ -40,6 +41,7 @@ repos:
- types-jinja2
- types-pyyaml
- types-requests
- types-setuptools
- uv
- validate-pyproject
- id: mypy
+10 -8
View File
@@ -3,27 +3,29 @@
from __future__ import annotations
import os
import subprocess
import sys
import textwrap
import time
from pathlib import Path
from subprocess import run
import click
def shell(cmd, *, check: bool, **kwargs):
return run([cmd], shell=True, check=check, **kwargs)
def shell(cmd: str, *, check: bool, **kwargs: object) -> subprocess.CompletedProcess[str]:
return subprocess.run([cmd], shell=True, check=check, **kwargs) # type: ignore[call-overload, no-any-return]
def git_repo_has_changes():
unstaged_changes = shell("git diff-index --quiet HEAD --", check=False).returncode != 0
staged_changes = shell("git diff-index --quiet --cached HEAD --", check=False).returncode != 0
def git_repo_has_changes() -> bool:
unstaged_changes: bool = shell("git diff-index --quiet HEAD --", check=False).returncode != 0
staged_changes: bool = (
shell("git diff-index --quiet --cached HEAD --", check=False).returncode != 0
)
return unstaged_changes or staged_changes
@click.command()
def main():
def main() -> None:
project_root = Path(__file__).parent / ".."
os.chdir(project_root)
@@ -57,7 +59,7 @@ def main():
PR generated by `{os.path.basename(__file__)}`.
"""
)
run(
subprocess.run(
[
"gh",
"pr",
+4 -2
View File
@@ -172,7 +172,9 @@ def get_projects(
return sorted((Project(item, github) for item in config), reverse=online)
def render_projects(projects: Sequence[Project], *, dest_path: Path, include_info: bool = True):
def render_projects(
projects: Sequence[Project], *, dest_path: Path, include_info: bool = True
) -> str:
io = StringIO()
print = functools.partial(builtins.print, file=io)
@@ -203,7 +205,7 @@ def insert_projects_table(
projects: Sequence[Project],
input_filename: str,
include_info: bool = True,
):
) -> None:
text = file.read_text()
projects_table = render_projects(projects, include_info=include_info, dest_path=file)
+8 -8
View File
@@ -4,29 +4,29 @@ from __future__ import annotations
import os
import shutil
import subprocess
import sys
import textwrap
import time
import typing
from glob import glob
from pathlib import Path
from subprocess import run
from urllib.parse import quote
import click
def shell(cmd, *, check: bool, **kwargs):
return run([cmd], shell=True, check=check, **kwargs)
def shell(cmd: str, *, check: bool, **kwargs: object) -> subprocess.CompletedProcess[str]:
return subprocess.run([cmd], shell=True, check=check, **kwargs) # type: ignore[call-overload, no-any-return]
def git_repo_has_changes():
def git_repo_has_changes() -> bool:
unstaged_changes = shell("git diff-index --quiet HEAD --", check=False).returncode != 0
staged_changes = shell("git diff-index --quiet --cached HEAD --", check=False).returncode != 0
return unstaged_changes or staged_changes
def generate_basic_project(path):
def generate_basic_project(path: Path) -> None:
sys.path.insert(0, "")
from test.test_projects.c import new_c_project
@@ -79,7 +79,7 @@ services = [
]
def ci_service_for_config_file(config_file):
def ci_service_for_config_file(config_file: str) -> CIService:
filename = Path(config_file).name
try:
@@ -134,7 +134,7 @@ def run_example_ci_configs(config_files=None):
dst_config_file.parent.mkdir(parents=True, exist_ok=True)
shutil.copyfile(src_config_file, dst_config_file)
run(["git", "add", example_project], check=True)
subprocess.run(["git", "add", example_project], check=True)
message = textwrap.dedent(
f"""\
Test example minimal configs
@@ -144,7 +144,7 @@ def run_example_ci_configs(config_files=None):
Time: {timestamp}
"""
)
run(["git", "commit", "--no-verify", "--message", message], check=True)
subprocess.run(["git", "commit", "--no-verify", "--message", message], check=True)
shell(f"git subtree --prefix={example_project} push origin {branch_name}", check=True)
print("---")
+1 -1
View File
@@ -18,7 +18,7 @@ except ImportError:
)
def main():
def main() -> None:
subprocess.run(["mkdocs", "build"], check=True)
hti = Html2Image(custom_flags=["--force-device-scale-factor=2"])
+1 -1
View File
@@ -20,7 +20,7 @@ README_CHANGELOG_SECTION = re.compile(
)
def main():
def main() -> None:
changelog_text = CHANGELOG_FILE.read_text()
readme_text = README_FILE.read_text()
+1 -1
View File
@@ -36,7 +36,7 @@ class VersionTuple:
version_string: str
def git_ls_remote_versions(url) -> list[VersionTuple]:
def git_ls_remote_versions(url: str) -> list[VersionTuple]:
versions: list[VersionTuple] = []
tags = subprocess.run(
["git", "ls-remote", "--tags", url], check=True, text=True, capture_output=True
+1 -1
View File
@@ -9,7 +9,7 @@ from typing import Any
def define_env(env: Any) -> None:
"Hook function for mkdocs-macros"
@env.macro
@env.macro # type: ignore[misc]
def subprocess_run(*args: str) -> str:
"Run a subprocess and return the stdout"
env = os.environ.copy()
+3 -16
View File
@@ -113,33 +113,20 @@ files = [
]
warn_unused_configs = true
warn_redundant_casts = true
no_implicit_reexport = true
strict_equality = true
warn_unused_ignores = true
check_untyped_defs = true
disallow_subclassing_any = true
disallow_any_generics = true
warn_return_any = true
no_implicit_optional = true
strict = true
disallow_untyped_defs = false
enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"]
warn_unreachable = true
warn_unreachable = false
[[tool.mypy.overrides]]
module = "cibuildwheel.*"
disallow_untyped_defs = true
disallow_untyped_calls = true
disallow_incomplete_defs = true
disallow_untyped_decorators = true
[[tool.mypy.overrides]]
module = [
"setuptools",
"setuptools._distutils", # needed even if only directly import setuptools._distutils.util
"setuptools._distutils.util",
"pytest", # ignored in pre-commit to speed up check
"bashlex",
"bashlex.*",
"importlib_resources",
+1 -1
View File
@@ -11,7 +11,7 @@ from cibuildwheel.util import detect_ci_provider, find_uv
from .utils import EMULATED_ARCHS, platform
def pytest_addoption(parser) -> None:
def pytest_addoption(parser: pytest.Parser) -> None:
parser.addoption(
"--run-emulation",
action="store",
+4 -4
View File
@@ -33,10 +33,9 @@ def test(tmp_path, capfd):
basic_project.generate(project_dir)
num_builds = len(utils.cibuildwheel_get_build_identifiers(project_dir))
if num_builds > 1:
expectation = pytest.raises(subprocess.CalledProcessError)
else:
expectation = does_not_raise()
expectation = (
pytest.raises(subprocess.CalledProcessError) if num_builds > 1 else does_not_raise()
)
with expectation as exc_info:
result = utils.cibuildwheel_run(
@@ -48,6 +47,7 @@ def test(tmp_path, capfd):
captured = capfd.readouterr()
if num_builds > 1:
assert exc_info is not None
assert "Build failed because a wheel named" in captured.err
assert exc_info.value.returncode == 6
else:
+3 -2
View File
@@ -3,6 +3,7 @@ from __future__ import annotations
import platform
import re
import textwrap
from pathlib import Path
import pytest
@@ -46,8 +47,8 @@ build-backend = "setuptools.build_meta"
VERSION_REGEX = r"([\w-]+)==([^\s]+)"
def get_versions_from_constraint_file(constraint_file):
constraint_file_text = constraint_file.read_text(encoding="utf8")
def get_versions_from_constraint_file(constraint_file: Path) -> dict[str, str]:
constraint_file_text = constraint_file.read_text(encoding="utf-8")
return dict(re.findall(VERSION_REGEX, constraint_file_text))
+6 -1
View File
@@ -4,6 +4,7 @@ import os
import subprocess
import sys
import textwrap
from collections.abc import Mapping
from pathlib import Path
from tempfile import TemporaryDirectory
@@ -28,7 +29,11 @@ def make_sdist(project: TestProject, working_dir: Path) -> Path:
return next(sdist_dir.glob("*.tar.gz"))
def cibuildwheel_from_sdist_run(sdist_path, add_env=None, config_file=None):
def cibuildwheel_from_sdist_run(
sdist_path: Path | str,
add_env: Mapping[str, str] | None = None,
config_file: str | None = None,
) -> list[str]:
env = os.environ.copy()
if add_env:
+1 -1
View File
@@ -8,7 +8,7 @@ from argparse import ArgumentParser
from pathlib import Path
def main():
def main() -> None:
parser = ArgumentParser(
prog="python -m test.test_projects", description="Generate a test project to check it out"
)
+3 -3
View File
@@ -22,11 +22,11 @@ class TestProject:
files: FilesDict
template_context: TemplateContext
def __init__(self):
def __init__(self) -> None:
self.files = {}
self.template_context = {}
def generate(self, path: Path):
def generate(self, path: Path) -> None:
for filename, content in self.files.items():
file_path = path / filename
file_path.parent.mkdir(parents=True, exist_ok=True)
@@ -37,7 +37,7 @@ class TestProject:
f.write(content)
def copy(self):
def copy(self) -> TestProject:
other = TestProject()
other.files = self.files.copy()
other.template_context = self.template_context.copy()
+7 -7
View File
@@ -78,13 +78,13 @@ version = 0.1.0
def new_c_project(
*,
spam_c_top_level_add="",
spam_c_function_add="",
setup_py_add="",
setup_py_extension_args_add="",
setup_py_setup_args_add="",
setup_cfg_add="",
):
spam_c_top_level_add: str = "",
spam_c_function_add: str = "",
setup_py_add: str = "",
setup_py_extension_args_add: str = "",
setup_py_setup_args_add: str = "",
setup_cfg_add: str = "",
) -> TestProject:
project = TestProject()
project.files.update(
+1 -1
View File
@@ -155,7 +155,7 @@ def test_failing_test(tmp_path):
@pytest.mark.parametrize("test_runner", ["pytest", "unittest"])
def test_bare_pytest_invocation(
tmp_path: Path, capfd: pytest.CaptureFixture[str], test_runner: str
):
) -> None:
"""Check that if a user runs pytest in the the test cwd, it raises a helpful error"""
project_dir = tmp_path / "project"
output_dir = tmp_path / "output"
+1 -1
View File
@@ -11,7 +11,7 @@ from . import test_projects, utils
basic_project = test_projects.new_c_project()
def skip_if_no_msvc(arm64=False):
def skip_if_no_msvc(arm64: bool = False) -> None:
programfiles = os.getenv("PROGRAMFILES(X86)", "") or os.getenv("PROGRAMFILES", "")
if not programfiles:
pytest.skip("Requires %PROGRAMFILES(X86)% variable to be set")
+29 -25
View File
@@ -10,8 +10,10 @@ import os
import platform as pm
import subprocess
import sys
from collections.abc import Mapping, Sequence
from pathlib import Path
from tempfile import TemporaryDirectory
from typing import Final
from typing import Any, Final
import pytest
@@ -37,7 +39,9 @@ else:
raise Exception(msg)
def cibuildwheel_get_build_identifiers(project_path, env=None, *, prerelease_pythons=False):
def cibuildwheel_get_build_identifiers(
project_path: Path, env: dict[str, str] | None = None, *, prerelease_pythons: bool = False
) -> list[str]:
"""
Returns the list of build identifiers that cibuildwheel will try to build
for the current platform.
@@ -75,14 +79,14 @@ def _update_pip_cache_dir(env: dict[str, str]) -> None:
def cibuildwheel_run(
project_path,
package_dir=".",
env=None,
add_env=None,
output_dir=None,
add_args=None,
single_python=False,
):
project_path: str | Path,
package_dir: str | Path = ".",
env: dict[str, str] | None = None,
add_env: Mapping[str, str] | None = None,
output_dir: Path | None = None,
add_args: Sequence[str] | None = None,
single_python: bool = False,
) -> list[str]:
"""
Runs cibuildwheel as a subprocess, building the project at project_path.
@@ -144,17 +148,17 @@ def _floor_macosx(*args: str) -> str:
def expected_wheels(
package_name,
package_version,
manylinux_versions=None,
musllinux_versions=None,
macosx_deployment_target="10.9",
machine_arch=None,
python_abi_tags=None,
include_universal2=False,
single_python=False,
single_arch=False,
):
package_name: str,
package_version: str,
manylinux_versions: list[str] | None = None,
musllinux_versions: list[str] | None = None,
macosx_deployment_target: str = "10.9",
machine_arch: str | None = None,
python_abi_tags: list[str] | None = None,
include_universal2: bool = False,
single_python: bool = False,
single_arch: bool = False,
) -> list[str]:
"""
Returns a list of expected wheels from a run of cibuildwheel.
"""
@@ -307,7 +311,7 @@ def expected_wheels(
return wheels
def get_macos_version():
def get_macos_version() -> tuple[int, int]:
"""
Returns the macOS major/minor version, as a tuple, e.g. (10, 15) or (11, 0)
@@ -316,10 +320,10 @@ def get_macos_version():
(11, 2) <= (11, 0) != True
"""
version_str, _, _ = pm.mac_ver()
return tuple(map(int, version_str.split(".")[:2]))
return tuple(map(int, version_str.split(".")[:2])) # type: ignore[return-value]
def skip_if_pyodide(reason: str):
def skip_if_pyodide(reason: str) -> Any:
return pytest.mark.skipif(platform == "pyodide", reason=reason)
@@ -330,7 +334,7 @@ def invoke_pytest() -> str:
return "pytest"
def arch_name_for_linux(arch: str):
def arch_name_for_linux(arch: str) -> str:
"""
Archs have different names on different platforms, but it's useful to be
able to run linux tests on dev machines. This function translates between
+6 -4
View File
@@ -4,12 +4,14 @@ import textwrap
from pathlib import Path
from pprint import pprint
import pytest
import cibuildwheel.linux
from cibuildwheel.oci_container import OCIContainerEngineConfig
from cibuildwheel.options import CommandLineArguments, Options
def test_linux_container_split(tmp_path: Path, monkeypatch):
def test_linux_container_split(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
"""
Tests splitting linux builds by container image, container engine, and before_all
"""
@@ -53,13 +55,13 @@ def test_linux_container_split(tmp_path: Path, monkeypatch):
build_steps = list(cibuildwheel.linux.get_build_steps(options, python_configurations))
# helper functions to extract test info
def identifiers(step):
def identifiers(step: cibuildwheel.linux.BuildStep) -> list[str]:
return [c.identifier for c in step.platform_configs]
def before_alls(step):
def before_alls(step: cibuildwheel.linux.BuildStep) -> list[str]:
return [options.build_options(c.identifier).before_all for c in step.platform_configs]
def container_engines(step):
def container_engines(step: cibuildwheel.linux.BuildStep) -> list[OCIContainerEngineConfig]:
return [options.build_options(c.identifier).container_engine for c in step.platform_configs]
pprint(build_steps)
+4 -4
View File
@@ -12,12 +12,12 @@ from cibuildwheel import linux, macos, util, windows
class ArgsInterceptor:
def __init__(self):
def __init__(self) -> None:
self.call_count = 0
self.args = None
self.kwargs = None
self.args: tuple[object, ...] | None = None
self.kwargs: dict[str, object] | None = None
def __call__(self, *args, **kwargs):
def __call__(self, *args: object, **kwargs: object) -> None:
self.call_count += 1
self.args = args
self.kwargs = kwargs
+1 -1
View File
@@ -119,7 +119,7 @@ def test_manylinux_images(
assert build_options.manylinux_images is None
def get_default_repair_command(platform):
def get_default_repair_command(platform: str) -> str:
if platform == "linux":
return "auditwheel repair -w {dest_dir} {wheel}"
elif platform == "macos":
+16 -7
View File
@@ -239,7 +239,7 @@ def test_binary_output(container_engine):
assert output == binary_data_string
def test_file_operation(tmp_path: Path, container_engine):
def test_file_operation(tmp_path: Path, container_engine: OCIContainerEngineConfig) -> None:
with OCIContainer(
engine=container_engine, image=DEFAULT_IMAGE, oci_platform=DEFAULT_OCI_PLATFORM
) as container:
@@ -259,7 +259,7 @@ def test_file_operation(tmp_path: Path, container_engine):
assert test_binary_data == bytes(output, encoding="utf8", errors="surrogateescape")
def test_dir_operations(tmp_path: Path, container_engine):
def test_dir_operations(tmp_path: Path, container_engine: OCIContainerEngineConfig) -> None:
with OCIContainer(
engine=container_engine, image=DEFAULT_IMAGE, oci_platform=DEFAULT_OCI_PLATFORM
) as container:
@@ -301,7 +301,7 @@ def test_dir_operations(tmp_path: Path, container_engine):
assert test_binary_data == (new_test_dir / "test.dat").read_bytes()
def test_environment_executor(container_engine):
def test_environment_executor(container_engine: OCIContainerEngineConfig) -> None:
with OCIContainer(
engine=container_engine, image=DEFAULT_IMAGE, oci_platform=DEFAULT_OCI_PLATFORM
) as container:
@@ -309,7 +309,9 @@ def test_environment_executor(container_engine):
assert assignment.evaluated_value({}, container.environment_executor) == "42"
def test_podman_vfs(tmp_path: Path, monkeypatch, container_engine):
def test_podman_vfs(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, container_engine: OCIContainerEngineConfig
) -> None:
if container_engine.name != "podman":
pytest.skip("only runs with podman")
if sys.platform.startswith("darwin"):
@@ -391,7 +393,7 @@ def test_podman_vfs(tmp_path: Path, monkeypatch, container_engine):
subprocess.run(["podman", "unshare", "rm", "-rf", vfs_path], check=True)
def test_create_args_volume(tmp_path: Path, container_engine):
def test_create_args_volume(tmp_path: Path, container_engine: OCIContainerEngineConfig) -> None:
if container_engine.name != "docker":
pytest.skip("only runs with docker")
@@ -513,7 +515,12 @@ def test_enforce_32_bit(container_engine):
("{name}; disable_host_mount: true", False),
],
)
def test_disable_host_mount(tmp_path: Path, container_engine, config, should_have_host_mount):
def test_disable_host_mount(
tmp_path: Path,
container_engine: OCIContainerEngineConfig,
config: str,
should_have_host_mount: bool,
) -> None:
if detect_ci_provider() in {CIProvider.circle_ci, CIProvider.gitlab}:
pytest.skip("Skipping test because docker on this platform does not support host mounts")
if sys.platform.startswith("darwin"):
@@ -536,7 +543,9 @@ def test_disable_host_mount(tmp_path: Path, container_engine, config, should_hav
@pytest.mark.parametrize("platform", list(OCIPlatform))
def test_local_image(container_engine, platform, tmp_path: Path):
def test_local_image(
container_engine: OCIContainerEngineConfig, platform: OCIPlatform, tmp_path: Path
) -> None:
if (
detect_ci_provider() in {CIProvider.travis_ci}
and pm in {"s390x", "ppc64le"}
+13 -7
View File
@@ -178,7 +178,7 @@ def test_toml_environment_evil(tmp_path, env_var_value):
(r'TEST_VAR="before\\$after"', "before$after"),
],
)
def test_toml_environment_quoting(tmp_path: Path, toml_assignment, result_value):
def test_toml_environment_quoting(tmp_path: Path, toml_assignment: str, result_value: str) -> None:
args = CommandLineArguments.defaults()
args.package_dir = tmp_path
@@ -255,8 +255,12 @@ def test_toml_environment_quoting(tmp_path: Path, toml_assignment, result_value)
],
)
def test_container_engine_option(
tmp_path: Path, toml_assignment, result_name, result_create_args, result_disable_host_mount
):
tmp_path: Path,
toml_assignment: str,
result_name: str,
result_create_args: tuple[str, ...],
result_disable_host_mount: bool,
) -> None:
args = CommandLineArguments.defaults()
args.package_dir = tmp_path
@@ -326,7 +330,9 @@ def test_environment_pass_references():
),
],
)
def test_build_frontend_option(tmp_path: Path, toml_assignment, result_name, result_args):
def test_build_frontend_option(
tmp_path: Path, toml_assignment: str, result_name: str, result_args: list[str]
) -> None:
args = CommandLineArguments.defaults()
args.package_dir = tmp_path
@@ -350,7 +356,7 @@ def test_build_frontend_option(tmp_path: Path, toml_assignment, result_name, res
assert parsed_build_frontend is None
def test_override_inherit_environment(tmp_path: Path):
def test_override_inherit_environment(tmp_path: Path) -> None:
args = CommandLineArguments.defaults()
args.package_dir = tmp_path
@@ -385,7 +391,7 @@ def test_override_inherit_environment(tmp_path: Path):
}
def test_override_inherit_environment_with_references(tmp_path: Path):
def test_override_inherit_environment_with_references(tmp_path: Path) -> None:
args = CommandLineArguments.defaults()
args.package_dir = tmp_path
@@ -434,7 +440,7 @@ def test_override_inherit_environment_with_references(tmp_path: Path):
)
def test_free_threaded_support(
tmp_path: Path, toml_assignment: str, env: dict[str, str], expected_result: bool
):
) -> None:
args = CommandLineArguments.defaults()
args.package_dir = tmp_path
+2 -2
View File
@@ -76,7 +76,7 @@ def test_prepare_command():
("foo-0.1-py38-none-win_amd64.whl", "pp310-win_amd64"),
],
)
def test_find_compatible_wheel_found(wheel: str, identifier: str):
def test_find_compatible_wheel_found(wheel: str, identifier: str) -> None:
wheel_ = PurePath(wheel)
found = find_compatible_wheel([wheel_], identifier)
assert found is wheel_
@@ -96,7 +96,7 @@ def test_find_compatible_wheel_found(wheel: str, identifier: str):
("foo-0.1-cp38-cp38-win_amd64.whl", "cp310-win_amd64"),
],
)
def test_find_compatible_wheel_not_found(wheel: str, identifier: str):
def test_find_compatible_wheel_not_found(wheel: str, identifier: str) -> None:
assert find_compatible_wheel([PurePath(wheel)], identifier) is None
+1 -1
View File
@@ -45,7 +45,7 @@ def test_validate_container_engine():
@pytest.mark.parametrize("platform", ["macos", "windows"])
def test_validate_bad_container_engine(platform: str):
def test_validate_bad_container_engine(platform: str) -> None:
"""
container-engine is not a valid option for macos or windows
"""
+1 -1
View File
@@ -27,7 +27,7 @@ def test_no_printout_on_error(tmp_path, capsys):
tmp_path.joinpath("example.1").touch()
raise RuntimeError()
captured = capsys.readouterr() # type: ignore[unreachable]
captured = capsys.readouterr()
assert captured.err == ""
assert "example.0" not in captured.out