chore: use kw_only (Python 3.10+) on most dataclasses (#2422)

* chore: use kw_only (Python 3.10+) on many dataclasses

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

* fix: expose windows file to type checker

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

---------

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
This commit is contained in:
Henry Schreiner
2025-05-28 10:41:26 +01:00
committed by GitHub
parent e18ab66cca
commit db2d59be22
16 changed files with 59 additions and 44 deletions
+2 -2
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import configparser import configparser
from dataclasses import dataclass import dataclasses
from pathlib import Path from pathlib import Path
import requests import requests
@@ -11,7 +11,7 @@ DIR = Path(__file__).parent.resolve()
RESOURCES = DIR.parent / "cibuildwheel/resources" RESOURCES = DIR.parent / "cibuildwheel/resources"
@dataclass(frozen=True) @dataclasses.dataclass(frozen=True)
class Image: class Image:
manylinux_version: str manylinux_version: str
platforms: list[str] platforms: list[str]
+2 -2
View File
@@ -1,10 +1,10 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import dataclasses
import difflib import difflib
import logging import logging
import tomllib import tomllib
from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from typing import Final from typing import Final
@@ -27,7 +27,7 @@ NODEJS_DIST: Final[str] = "https://nodejs.org/dist/"
NODEJS_INDEX: Final[str] = f"{NODEJS_DIST}index.json" NODEJS_INDEX: Final[str] = f"{NODEJS_DIST}index.json"
@dataclass(frozen=True, order=True) @dataclasses.dataclass(frozen=True, order=True)
class VersionTuple: class VersionTuple:
version: Version version: Version
version_string: str version_string: str
+2 -2
View File
@@ -1,11 +1,11 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import dataclasses
import difflib import difflib
import logging import logging
import subprocess import subprocess
import tomllib import tomllib
from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from typing import Final from typing import Final
@@ -28,7 +28,7 @@ GET_VIRTUALENV_URL_TEMPLATE: Final[str] = (
) )
@dataclass(frozen=True, order=True) @dataclasses.dataclass(frozen=True, order=True)
class VersionTuple: class VersionTuple:
version: Version version: Version
version_string: str version_string: str
+2 -2
View File
@@ -1,3 +1,4 @@
import dataclasses
import subprocess import subprocess
from collections.abc import ( from collections.abc import (
Callable, Callable,
@@ -5,7 +6,6 @@ from collections.abc import (
Mapping, Mapping,
Sequence, Sequence,
) )
from dataclasses import dataclass
import bashlex import bashlex
@@ -17,7 +17,7 @@ def local_environment_executor(command: Sequence[str], env: Mapping[str, str]) -
return subprocess.run(command, env=env, text=True, stdout=subprocess.PIPE, check=True).stdout return subprocess.run(command, env=env, text=True, stdout=subprocess.PIPE, check=True).stdout
@dataclass(frozen=True) @dataclasses.dataclass(frozen=True, kw_only=True)
class NodeExecutionContext: class NodeExecutionContext:
environment: dict[str, str] environment: dict[str, str]
input: str input: str
+1 -1
View File
@@ -102,7 +102,7 @@ class EnvironmentAssignmentBash:
return False return False
@dataclasses.dataclass @dataclasses.dataclass(kw_only=True)
class ParsedEnvironment: class ParsedEnvironment:
assignments: list[EnvironmentAssignment] assignments: list[EnvironmentAssignment]
+2 -2
View File
@@ -1,7 +1,7 @@
import dataclasses
import shlex import shlex
import typing import typing
from collections.abc import Sequence from collections.abc import Sequence
from dataclasses import dataclass
from typing import Literal, Self, get_args from typing import Literal, Self, get_args
from .logger import log from .logger import log
@@ -10,7 +10,7 @@ from .util.helpers import parse_key_value_string
BuildFrontendName = Literal["pip", "build", "build[uv]"] BuildFrontendName = Literal["pip", "build", "build[uv]"]
@dataclass(frozen=True) @dataclasses.dataclass(frozen=True)
class BuildFrontendConfig: class BuildFrontendConfig:
name: BuildFrontendName name: BuildFrontendName
args: Sequence[str] = () args: Sequence[str] = ()
+4 -3
View File
@@ -1,3 +1,4 @@
import dataclasses
import io import io
import json import json
import os import os
@@ -10,7 +11,6 @@ import textwrap
import typing import typing
import uuid import uuid
from collections.abc import Mapping, Sequence from collections.abc import Mapping, Sequence
from dataclasses import dataclass, field
from enum import Enum from enum import Enum
from pathlib import Path, PurePath, PurePosixPath from pathlib import Path, PurePath, PurePosixPath
from types import TracebackType from types import TracebackType
@@ -37,10 +37,11 @@ class OCIPlatform(Enum):
S390X = "linux/s390x" S390X = "linux/s390x"
@dataclass(frozen=True) @dataclasses.dataclass(frozen=True)
class OCIContainerEngineConfig: class OCIContainerEngineConfig:
name: ContainerEngineName name: ContainerEngineName
create_args: tuple[str, ...] = field(default_factory=tuple) _: dataclasses.KW_ONLY
create_args: tuple[str, ...] = dataclasses.field(default_factory=tuple)
disable_host_mount: bool = False disable_host_mount: bool = False
@classmethod @classmethod
+3 -3
View File
@@ -51,7 +51,7 @@ MUSLLINUX_ARCHS: Final[tuple[str, ...]] = (
) )
@dataclasses.dataclass @dataclasses.dataclass(kw_only=True)
class CommandLineArguments: class CommandLineArguments:
platform: Literal["auto", "linux", "macos", "windows"] | None platform: Literal["auto", "linux", "macos", "windows"] | None
archs: str | None archs: str | None
@@ -80,7 +80,7 @@ class CommandLineArguments:
) )
@dataclasses.dataclass(frozen=True) @dataclasses.dataclass(frozen=True, kw_only=True)
class GlobalOptions: class GlobalOptions:
package_dir: Path package_dir: Path
output_dir: Path output_dir: Path
@@ -90,7 +90,7 @@ class GlobalOptions:
allow_empty: bool allow_empty: bool
@dataclasses.dataclass(frozen=True) @dataclasses.dataclass(frozen=True, kw_only=True)
class BuildOptions: class BuildOptions:
globals: GlobalOptions globals: GlobalOptions
environment: ParsedEnvironment environment: ParsedEnvironment
+2 -2
View File
@@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
import dataclasses
import os import os
import shlex import shlex
import shutil import shutil
@@ -7,7 +8,6 @@ import subprocess
import sys import sys
import textwrap import textwrap
from collections.abc import Sequence, Set from collections.abc import Sequence, Set
from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from typing import assert_never from typing import assert_never
@@ -42,7 +42,7 @@ from ..venv import constraint_flags, virtualenv
from .macos import install_cpython as install_build_cpython from .macos import install_cpython as install_build_cpython
@dataclass(frozen=True) @dataclasses.dataclass(frozen=True, kw_only=True)
class PythonConfiguration: class PythonConfiguration:
version: str version: str
identifier: str identifier: str
+3 -3
View File
@@ -1,10 +1,10 @@
import contextlib import contextlib
import dataclasses
import subprocess import subprocess
import sys import sys
import textwrap import textwrap
from collections import OrderedDict from collections import OrderedDict
from collections.abc import Iterable, Iterator, Sequence, Set from collections.abc import Iterable, Iterator, Sequence, Set
from dataclasses import dataclass
from pathlib import Path, PurePath, PurePosixPath from pathlib import Path, PurePath, PurePosixPath
from typing import assert_never from typing import assert_never
@@ -32,7 +32,7 @@ ARCHITECTURE_OCI_PLATFORM_MAP = {
} }
@dataclass(frozen=True) @dataclasses.dataclass(frozen=True, kw_only=True)
class PythonConfiguration: class PythonConfiguration:
version: str version: str
identifier: str identifier: str
@@ -43,7 +43,7 @@ class PythonConfiguration:
return PurePosixPath(self.path_str) return PurePosixPath(self.path_str)
@dataclass(frozen=True) @dataclasses.dataclass(frozen=True, kw_only=True)
class BuildStep: class BuildStep:
platform_configs: list[PythonConfiguration] platform_configs: list[PythonConfiguration]
platform_tag: str platform_tag: str
+2 -2
View File
@@ -1,3 +1,4 @@
import dataclasses
import functools import functools
import inspect import inspect
import os import os
@@ -8,7 +9,6 @@ import subprocess
import sys import sys
import typing import typing
from collections.abc import Set from collections.abc import Set
from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from typing import Literal, assert_never from typing import Literal, assert_never
@@ -76,7 +76,7 @@ def get_macos_sdks() -> list[str]:
return [m.group(1) for m in re.finditer(r"-sdk (macosx\S+)", output)] return [m.group(1) for m in re.finditer(r"-sdk (macosx\S+)", output)]
@dataclass(frozen=True) @dataclasses.dataclass(frozen=True, kw_only=True)
class PythonConfiguration: class PythonConfiguration:
version: str version: str
identifier: str identifier: str
+2 -2
View File
@@ -1,3 +1,4 @@
import dataclasses
import functools import functools
import json import json
import os import os
@@ -6,7 +7,6 @@ import sys
import tomllib import tomllib
import typing import typing
from collections.abc import Set from collections.abc import Set
from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from tempfile import TemporaryDirectory from tempfile import TemporaryDirectory
from typing import Final, TypedDict from typing import Final, TypedDict
@@ -41,7 +41,7 @@ from ..venv import constraint_flags, virtualenv
IS_WIN: Final[bool] = sys.platform.startswith("win") IS_WIN: Final[bool] = sys.platform.startswith("win")
@dataclass(frozen=True) @dataclasses.dataclass(frozen=True, kw_only=True)
class PythonConfiguration: class PythonConfiguration:
version: str version: str
identifier: str identifier: str
+2 -2
View File
@@ -1,3 +1,4 @@
import dataclasses
import json import json
import os import os
import platform as platform_module import platform as platform_module
@@ -5,7 +6,6 @@ import shutil
import subprocess import subprocess
import textwrap import textwrap
from collections.abc import MutableMapping, Set from collections.abc import MutableMapping, Set
from dataclasses import dataclass
from functools import cache from functools import cache
from pathlib import Path from pathlib import Path
from typing import assert_never from typing import assert_never
@@ -51,7 +51,7 @@ def get_nuget_args(
] ]
@dataclass(frozen=True) @dataclasses.dataclass(frozen=True, kw_only=True)
class PythonConfiguration: class PythonConfiguration:
version: str version: str
arch: str arch: str
+3 -3
View File
@@ -1,5 +1,5 @@
import dataclasses
import itertools import itertools
from dataclasses import dataclass
from enum import StrEnum from enum import StrEnum
from fnmatch import fnmatch from fnmatch import fnmatch
from typing import Any from typing import Any
@@ -58,7 +58,7 @@ class EnableGroup(StrEnum):
return frozenset(result) return frozenset(result)
@dataclass(frozen=True, kw_only=True) @dataclasses.dataclass(frozen=True, kw_only=True)
class BuildSelector: class BuildSelector:
""" """
This class holds a set of build/skip patterns. You call an instance with a This class holds a set of build/skip patterns. You call an instance with a
@@ -113,7 +113,7 @@ class BuildSelector:
} }
@dataclass(frozen=True) @dataclasses.dataclass(frozen=True, kw_only=True)
class TestSelector: class TestSelector:
""" """
A build selector that can only skip tests according to a skip pattern. A build selector that can only skip tests according to a skip pattern.
+1 -1
View File
@@ -11,7 +11,7 @@ from .cmd import call
from .helpers import parse_key_value_string, unwrap from .helpers import parse_key_value_string, unwrap
@dataclass() @dataclass(kw_only=True)
class DependencyConstraints: class DependencyConstraints:
base_file_path: Path | None = None base_file_path: Path | None = None
packages: list[str] = field(default_factory=list) packages: list[str] = field(default_factory=list)
+26 -12
View File
@@ -1,6 +1,8 @@
import contextlib import contextlib
import sys import sys
from collections.abc import Generator
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING
import pytest import pytest
import setuptools._distutils.util import setuptools._distutils.util
@@ -10,23 +12,27 @@ from cibuildwheel.errors import FatalError
from cibuildwheel.platforms.windows import PythonConfiguration, setup_setuptools_cross_compile from cibuildwheel.platforms.windows import PythonConfiguration, setup_setuptools_cross_compile
# monkeypatching os.name is too flaky. E.g. It works on my machine, but fails in pipeline # monkeypatching os.name is too flaky. E.g. It works on my machine, but fails in pipeline
if not sys.platform.startswith("win"): if not sys.platform.startswith("win") and not TYPE_CHECKING:
pytest.skip("Windows-only tests", allow_module_level=True) pytest.skip("Windows-only tests", allow_module_level=True)
@contextlib.contextmanager @contextlib.contextmanager
def patched_environment(monkeypatch: pytest.MonkeyPatch, environment: dict[str, str]): def patched_environment(
monkeypatch: pytest.MonkeyPatch, environment: dict[str, str]
) -> Generator[None, None, None]:
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
for envvar, val in environment.items(): for envvar, val in environment.items():
mp.setenv(name=envvar, value=val) mp.setenv(name=envvar, value=val)
yield yield
def test_x86(tmp_path: Path, monkeypatch: pytest.MonkeyPatch): def test_x86(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
arch = "32" arch = "32"
environment: dict[str, str] = {} environment: dict[str, str] = {}
configuration = PythonConfiguration("irrelevant", arch, "irrelevant", None) configuration = PythonConfiguration(
version="irrelevant", arch=arch, identifier="irrelevant", url=None
)
setup_setuptools_cross_compile(tmp_path, configuration, tmp_path, environment) setup_setuptools_cross_compile(tmp_path, configuration, tmp_path, environment)
with patched_environment(monkeypatch, environment): with patched_environment(monkeypatch, environment):
@@ -36,11 +42,13 @@ def test_x86(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
assert target_platform == "win32" assert target_platform == "win32"
def test_x64(tmp_path: Path, monkeypatch: pytest.MonkeyPatch): def test_x64(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
arch = "64" arch = "64"
environment: dict[str, str] = {} environment: dict[str, str] = {}
configuration = PythonConfiguration("irrelevant", arch, "irrelevant", None) configuration = PythonConfiguration(
version="irrelevant", arch=arch, identifier="irrelevant", url=None
)
setup_setuptools_cross_compile(tmp_path, configuration, tmp_path, environment) setup_setuptools_cross_compile(tmp_path, configuration, tmp_path, environment)
with patched_environment(monkeypatch, environment): with patched_environment(monkeypatch, environment):
@@ -53,11 +61,13 @@ def test_x64(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
@pytest.mark.skipif( @pytest.mark.skipif(
detect_ci_provider() == CIProvider.azure_pipelines, reason="arm64 not recognised on azure" detect_ci_provider() == CIProvider.azure_pipelines, reason="arm64 not recognised on azure"
) )
def test_arm(tmp_path: Path, monkeypatch: pytest.MonkeyPatch): def test_arm(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
arch = "ARM64" arch = "ARM64"
environment: dict[str, str] = {} environment: dict[str, str] = {}
configuration = PythonConfiguration("irrelevant", arch, "irrelevant", None) configuration = PythonConfiguration(
version="irrelevant", arch=arch, identifier="irrelevant", url=None
)
setup_setuptools_cross_compile(tmp_path, configuration, tmp_path, environment) setup_setuptools_cross_compile(tmp_path, configuration, tmp_path, environment)
with patched_environment(monkeypatch, environment): with patched_environment(monkeypatch, environment):
@@ -67,21 +77,25 @@ def test_arm(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
assert target_platform == "win-arm64" assert target_platform == "win-arm64"
def test_env_set(tmp_path: Path): def test_env_set(tmp_path: Path) -> None:
arch = "32" arch = "32"
environment = {"VSCMD_ARG_TGT_ARCH": "x64"} environment = {"VSCMD_ARG_TGT_ARCH": "x64"}
configuration = PythonConfiguration("irrelevant", arch, "irrelevant", None) configuration = PythonConfiguration(
version="irrelevant", arch=arch, identifier="irrelevant", url=None
)
with pytest.raises(FatalError, match="VSCMD_ARG_TGT_ARCH"): with pytest.raises(FatalError, match="VSCMD_ARG_TGT_ARCH"):
setup_setuptools_cross_compile(tmp_path, configuration, tmp_path, environment) setup_setuptools_cross_compile(tmp_path, configuration, tmp_path, environment)
def test_env_blank(tmp_path: Path, monkeypatch: pytest.MonkeyPatch): def test_env_blank(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
arch = "32" arch = "32"
environment = {"VSCMD_ARG_TGT_ARCH": ""} environment = {"VSCMD_ARG_TGT_ARCH": ""}
configuration = PythonConfiguration("irrelevant", arch, "irrelevant", None) configuration = PythonConfiguration(
version="irrelevant", arch=arch, identifier="irrelevant", url=None
)
setup_setuptools_cross_compile(tmp_path, configuration, tmp_path, environment) setup_setuptools_cross_compile(tmp_path, configuration, tmp_path, environment)
with patched_environment(monkeypatch, environment): with patched_environment(monkeypatch, environment):