From 98fedb7e5142c9d4f591c65dffb6b21a276fb67c Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 28 Apr 2022 09:19:27 -0400 Subject: [PATCH] refactor: use dataclasses vs. NamedTuples --- .pre-commit-config.yaml | 4 ++-- bin/update_docker.py | 5 +++-- bin/update_virtualenv.py | 5 +++-- cibuildwheel/__main__.py | 2 +- cibuildwheel/bashlex_eval.py | 6 ++++-- cibuildwheel/linux.py | 9 ++++++--- cibuildwheel/macos.py | 6 ++++-- cibuildwheel/options.py | 31 +++++++++++++------------------ cibuildwheel/util.py | 19 ++++++++++--------- cibuildwheel/windows.py | 6 ++++-- unit_test/utils.py | 20 ++++++++++---------- 11 files changed, 60 insertions(+), 53 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c64525ee..da8b94a2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,7 +27,7 @@ repos: # Autoremoves unused imports - repo: https://github.com/hadialqattan/pycln - rev: v1.3.1 + rev: v1.3.2 hooks: - id: pycln args: [--all] @@ -49,7 +49,7 @@ repos: - id: setup-cfg-fmt - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.942 + rev: v0.950 hooks: - id: mypy name: mypy 3.6 on cibuildwheel/ diff --git a/bin/update_docker.py b/bin/update_docker.py index 73da5a1b..105a3267 100755 --- a/bin/update_docker.py +++ b/bin/update_docker.py @@ -2,8 +2,8 @@ from __future__ import annotations import configparser +from dataclasses import dataclass from pathlib import Path -from typing import NamedTuple import requests @@ -11,7 +11,8 @@ DIR = Path(__file__).parent.resolve() RESOURCES = DIR.parent / "cibuildwheel/resources" -class Image(NamedTuple): +@dataclass(frozen=True) +class Image: manylinux_version: str platform: str image_name: str diff --git a/bin/update_virtualenv.py b/bin/update_virtualenv.py index 2e67007e..8dd9c5ef 100755 --- a/bin/update_virtualenv.py +++ b/bin/update_virtualenv.py @@ -6,8 +6,8 @@ import difflib import logging import subprocess import sys +from dataclasses import dataclass from pathlib import Path -from typing import NamedTuple import click import rich @@ -36,7 +36,8 @@ GET_VIRTUALENV_URL_TEMPLATE: Final[ ] = f"{GET_VIRTUALENV_GITHUB}/blob/{{version}}/public/virtualenv.pyz?raw=true" -class VersionTuple(NamedTuple): +@dataclass(frozen=True) +class VersionTuple: version: Version version_string: str diff --git a/cibuildwheel/__main__.py b/cibuildwheel/__main__.py index 306e73ab..9f24f000 100644 --- a/cibuildwheel/__main__.py +++ b/cibuildwheel/__main__.py @@ -107,7 +107,7 @@ def main() -> None: help="Enable pre-release Python versions if available.", ) - args = parser.parse_args(namespace=CommandLineArguments()) + args = CommandLineArguments(**vars(parser.parse_args())) if args.platform != "auto": platform = args.platform diff --git a/cibuildwheel/bashlex_eval.py b/cibuildwheel/bashlex_eval.py index 9eb5eac3..a8b4691c 100644 --- a/cibuildwheel/bashlex_eval.py +++ b/cibuildwheel/bashlex_eval.py @@ -1,5 +1,6 @@ import subprocess -from typing import Callable, Dict, List, NamedTuple, Optional, Sequence +from dataclasses import dataclass +from typing import Callable, Dict, List, Optional, Sequence import bashlex @@ -13,7 +14,8 @@ def local_environment_executor(command: List[str], env: Dict[str, str]) -> str: ).stdout -class NodeExecutionContext(NamedTuple): +@dataclass(frozen=True) +class NodeExecutionContext: environment: Dict[str, str] input: str executor: EnvironmentExecutor diff --git a/cibuildwheel/linux.py b/cibuildwheel/linux.py index 8478bf09..a1862d77 100644 --- a/cibuildwheel/linux.py +++ b/cibuildwheel/linux.py @@ -1,8 +1,9 @@ import subprocess import sys import textwrap +from dataclasses import dataclass from pathlib import Path, PurePath -from typing import Iterator, List, NamedTuple, Set, Tuple +from typing import Iterator, List, Set, Tuple from .architecture import Architecture from .docker_container import DockerContainer @@ -18,7 +19,8 @@ from .util import ( ) -class PythonConfiguration(NamedTuple): +@dataclass(frozen=True) +class PythonConfiguration: version: str identifier: str path_str: str @@ -28,7 +30,8 @@ class PythonConfiguration(NamedTuple): return PurePath(self.path_str) -class BuildStep(NamedTuple): +@dataclass(frozen=True) +class BuildStep: platform_configs: List[PythonConfiguration] platform_tag: str docker_image: str diff --git a/cibuildwheel/macos.py b/cibuildwheel/macos.py index d2770315..cf9adba1 100644 --- a/cibuildwheel/macos.py +++ b/cibuildwheel/macos.py @@ -5,8 +5,9 @@ import re import shutil import subprocess import sys +from dataclasses import dataclass from pathlib import Path -from typing import Dict, List, NamedTuple, Sequence, Set, Tuple, cast +from typing import Dict, List, Sequence, Set, Tuple, cast from filelock import FileLock @@ -53,7 +54,8 @@ def get_macos_sdks() -> List[str]: return [m.group(1) for m in re.finditer(r"-sdk (macosx\S+)", output)] -class PythonConfiguration(NamedTuple): +@dataclass(frozen=True) +class PythonConfiguration: version: str identifier: str url: str diff --git a/cibuildwheel/options.py b/cibuildwheel/options.py index ce4f5a77..0385fc3e 100644 --- a/cibuildwheel/options.py +++ b/cibuildwheel/options.py @@ -4,24 +4,15 @@ import sys import traceback from configparser import ConfigParser from contextlib import contextmanager +from dataclasses import asdict, dataclass from pathlib import Path -from typing import ( - Any, - Dict, - Iterator, - List, - Mapping, - NamedTuple, - Optional, - Set, - Tuple, - Union, -) +from typing import Any, Dict, Iterator, List, Mapping, Optional, Set, Tuple, Union if sys.version_info >= (3, 11): import tomllib else: import tomli as tomllib + from packaging.specifiers import SpecifierSet from .architecture import Architecture @@ -43,6 +34,7 @@ from .util import ( ) +@dataclass class CommandLineArguments: platform: Literal["auto", "linux", "macos", "windows"] archs: Optional[str] @@ -54,7 +46,8 @@ class CommandLineArguments: prerelease_pythons: bool -class GlobalOptions(NamedTuple): +@dataclass(frozen=True) +class GlobalOptions: package_dir: Path output_dir: Path build_selector: BuildSelector @@ -62,7 +55,8 @@ class GlobalOptions(NamedTuple): architectures: Set[Architecture] -class BuildOptions(NamedTuple): +@dataclass(frozen=True) +class BuildOptions: globals: GlobalOptions environment: ParsedEnvironment before_all: str @@ -102,7 +96,8 @@ class BuildOptions(NamedTuple): Setting = Union[Dict[str, str], List[str], str, int] -class Override(NamedTuple): +@dataclass(frozen=True) +class Override: select_pattern: str options: Dict[str, Setting] @@ -550,12 +545,12 @@ class Options: def summary(self, identifiers: List[str]) -> str: lines = [ f"{option_name}: {option_value!r}" - for option_name, option_value in sorted(self.globals._asdict().items()) + for option_name, option_value in sorted(asdict(self.globals).items()) ] build_option_defaults = self.build_options(identifier=None) - for option_name, default_value in sorted(build_option_defaults._asdict().items()): + for option_name, default_value in sorted(asdict(build_option_defaults).items()): if option_name == "globals": continue @@ -563,7 +558,7 @@ class Options: # if any identifiers have an overridden value, print that too for identifier in identifiers: - option_value = self.build_options(identifier=identifier)._asdict()[option_name] + option_value = getattr(self.build_options(identifier=identifier), option_name) if option_value != default_value: lines.append(f" {identifier}: {option_value!r}") diff --git a/cibuildwheel/util.py b/cibuildwheel/util.py index 5015d348..5ce85bf8 100644 --- a/cibuildwheel/util.py +++ b/cibuildwheel/util.py @@ -1,5 +1,4 @@ import contextlib -import dataclasses import fnmatch import itertools import os @@ -11,6 +10,7 @@ import sys import textwrap import time import urllib.request +from dataclasses import dataclass from enum import Enum from functools import lru_cache from pathlib import Path @@ -22,7 +22,6 @@ from typing import ( Iterable, Iterator, List, - NamedTuple, Optional, Sequence, TextIO, @@ -228,7 +227,7 @@ def selector_matches(patterns: str, string: str) -> bool: # Once we require Python 3.10+, we can add kw_only=True -@dataclasses.dataclass +@dataclass(frozen=True) class IdentifierSelector: """ This class holds a set of build/skip patterns. You call an instance with a @@ -266,14 +265,14 @@ class IdentifierSelector: return should_build and not should_skip -@dataclasses.dataclass +@dataclass(frozen=True) class BuildSelector(IdentifierSelector): pass # Note that requires-python is not needed for TestSelector, as you can't test # what you can't build. -@dataclasses.dataclass +@dataclass(frozen=True) class TestSelector(IdentifierSelector): build_config: str = "*" @@ -413,6 +412,12 @@ def unwrap(text: str) -> str: return re.sub(r"\s+", " ", text) +@dataclass(frozen=True) +class FileReport: + name: str + size: str + + @contextlib.contextmanager def print_new_wheels(msg: str, output_dir: Path) -> Iterator[None]: """ @@ -427,10 +432,6 @@ def print_new_wheels(msg: str, output_dir: Path) -> Iterator[None]: yield final_contents = set(output_dir.iterdir()) - class FileReport(NamedTuple): - name: str - size: str - new_contents = [ FileReport(wheel.name, f"{(wheel.stat().st_size + 1023) // 1024:,d}") for wheel in final_contents - existing_contents diff --git a/cibuildwheel/windows.py b/cibuildwheel/windows.py index 13b5c0c3..ef9742ee 100644 --- a/cibuildwheel/windows.py +++ b/cibuildwheel/windows.py @@ -2,9 +2,10 @@ import os import shutil import subprocess import sys +from dataclasses import dataclass from functools import lru_cache from pathlib import Path -from typing import Dict, List, NamedTuple, Optional, Sequence, Set +from typing import Dict, List, Optional, Sequence, Set from zipfile import ZipFile from filelock import FileLock @@ -45,7 +46,8 @@ def get_nuget_args(version: str, arch: str, output_directory: Path) -> List[str] ] -class PythonConfiguration(NamedTuple): +@dataclass(frozen=True) +class PythonConfiguration: version: str arch: str identifier: str diff --git a/unit_test/utils.py b/unit_test/utils.py index 61833fa2..32e9331f 100644 --- a/unit_test/utils.py +++ b/unit_test/utils.py @@ -2,15 +2,15 @@ from cibuildwheel.options import CommandLineArguments def get_default_command_line_arguments() -> CommandLineArguments: - defaults = CommandLineArguments() - - defaults.platform = "auto" - defaults.allow_empty = False - defaults.archs = None - defaults.config_file = "" - defaults.output_dir = None - defaults.package_dir = "." - defaults.prerelease_pythons = False - defaults.print_build_identifiers = False + defaults = CommandLineArguments( + platform="auto", + allow_empty=False, + archs=None, + config_file="", + output_dir=None, + package_dir=".", + prerelease_pythons=False, + print_build_identifiers=False, + ) return defaults