diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index eb38f038..8d039f8b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -58,6 +58,7 @@ repos: - types-jinja2 - types-certifi - types-toml + - tomli - id: mypy name: mypy 3.7+ on bin/ files: ^((bin|docs)/.*py|noxfile.py)$ @@ -69,7 +70,7 @@ repos: - types-pyyaml - types-click - types-requests - - types-toml + - tomli - repo: https://github.com/PyCQA/flake8 rev: 4.0.1 diff --git a/bin/update_pythons.py b/bin/update_pythons.py index 457d63a9..b1b2c95a 100755 --- a/bin/update_pythons.py +++ b/bin/update_pythons.py @@ -11,13 +11,13 @@ from typing import Any, Union import click import requests import rich -import toml +import tomli from packaging.specifiers import Specifier from packaging.version import Version from rich.logging import RichHandler from rich.syntax import Syntax -from cibuildwheel.extra import InlineArrayDictEncoder +from cibuildwheel.extra import dump_python_configurations from cibuildwheel.typing import Final, Literal, TypedDict log = logging.getLogger("cibw") @@ -291,7 +291,8 @@ def update_pythons(force: bool, level: str) -> None: toml_file_path = RESOURCES_DIR / "build-platforms.toml" original_toml = toml_file_path.read_text() - configs = toml.loads(original_toml) + with toml_file_path.open("rb") as f: + configs = tomli.load(f) for config in configs["windows"]["python_configurations"]: all_versions.update_config(config) @@ -299,7 +300,7 @@ def update_pythons(force: bool, level: str) -> None: for config in configs["macos"]["python_configurations"]: all_versions.update_config(config) - result_toml = toml.dumps(configs, encoder=InlineArrayDictEncoder()) # type: ignore + result_toml = dump_python_configurations(configs) rich.print() # spacer diff --git a/cibuildwheel/extra.py b/cibuildwheel/extra.py index ea106130..60eecbad 100644 --- a/cibuildwheel/extra.py +++ b/cibuildwheel/extra.py @@ -2,22 +2,31 @@ These are utilities for the `/bin` scripts, not for the `cibuildwheel` program. """ -from typing import Any, Dict +from io import StringIO +from typing import Dict, List -import toml.encoder -from packaging.version import Version +from .typing import Protocol + +__all__ = ("Printable", "dump_python_configurations") -class InlineArrayDictEncoder(toml.encoder.TomlEncoder): # type: ignore - def __init__(self) -> None: - super().__init__() - self.dump_funcs[Version] = lambda v: f'"{v}"' +class Printable(Protocol): + def __str__(self) -> str: + ... - def dump_sections(self, o: Dict[str, Any], sup: str) -> Any: - if not all(isinstance(a, list) for a in o.values()): - return super().dump_sections(o, sup) - val = "" - for k, v in o.items(): - inner = ",\n ".join(self.dump_inline_table(d_i).strip() for d_i in v) - val += f"{k} = [\n {inner},\n]\n" - return val, self._dict() + +def dump_python_configurations(inp: Dict[str, Dict[str, List[Dict[str, Printable]]]]) -> str: + output = StringIO() + for header, values in inp.items(): + output.write(f"[{header}]\n") + for inner_header, listing in values.items(): + output.write(f"{inner_header} = [\n") + for item in listing: + output.write(" { ") + dict_contents = (f'{key} = "{value}"' for key, value in item.items()) + output.write(", ".join(dict_contents)) + output.write(" },\n") + output.write("]\n") + output.write("\n") + # Strip the final newline, to avoid two blank lines at the end. + return output.getvalue()[:-1] diff --git a/cibuildwheel/options.py b/cibuildwheel/options.py index 164cd409..6a1c19e3 100644 --- a/cibuildwheel/options.py +++ b/cibuildwheel/options.py @@ -5,7 +5,7 @@ from configparser import ConfigParser from pathlib import Path from typing import Any, Dict, List, Mapping, Optional, Set, Tuple, Union -import toml +import tomli from packaging.specifiers import SpecifierSet from .architecture import Architecture @@ -143,7 +143,8 @@ class ConfigOptions: """ Load a toml file, returns global and platform as separate dicts. """ - config = toml.load(filename) + with filename.open("rb") as f: + config = tomli.load(f) global_options = config.get("tool", {}).get("cibuildwheel", {}) platform_options = global_options.get(self.platform, {}) diff --git a/cibuildwheel/projectfiles.py b/cibuildwheel/projectfiles.py index dd6efd07..6dda6582 100644 --- a/cibuildwheel/projectfiles.py +++ b/cibuildwheel/projectfiles.py @@ -4,7 +4,7 @@ from configparser import ConfigParser from pathlib import Path from typing import Any, Optional -import toml +import tomli if sys.version_info < (3, 8): Constant = ast.Str @@ -56,7 +56,8 @@ def get_requires_python_str(package_dir: Path) -> Optional[str]: # Read in from pyproject.toml:project.requires-python try: - info = toml.load(package_dir / "pyproject.toml") + with (package_dir / "pyproject.toml").open("rb") as f1: + info = tomli.load(f1) return str(info["project"]["requires-python"]) except (FileNotFoundError, KeyError, IndexError, TypeError): pass @@ -70,8 +71,8 @@ def get_requires_python_str(package_dir: Path) -> Optional[str]: pass try: - with open(package_dir / "setup.py") as f: - return setup_py_python_requires(f.read()) + with (package_dir / "setup.py").open() as f2: + return setup_py_python_requires(f2.read()) except FileNotFoundError: pass diff --git a/cibuildwheel/typing.py b/cibuildwheel/typing.py index b8e91764..b9bd207b 100644 --- a/cibuildwheel/typing.py +++ b/cibuildwheel/typing.py @@ -4,9 +4,9 @@ import sys from typing import TYPE_CHECKING, NoReturn, Set, Union if sys.version_info < (3, 8): - from typing_extensions import Final, Literal, TypedDict + from typing_extensions import Final, Literal, Protocol, TypedDict else: - from typing import Final, Literal, TypedDict + from typing import Final, Literal, Protocol, TypedDict __all__ = ( @@ -18,6 +18,7 @@ __all__ = ( "PopenBytes", "PathOrStr", "PlatformName", + "Protocol", "PLATFORMS", "assert_never", ) diff --git a/cibuildwheel/util.py b/cibuildwheel/util.py index a5896ebd..016d559d 100644 --- a/cibuildwheel/util.py +++ b/cibuildwheel/util.py @@ -16,7 +16,7 @@ from typing import Dict, Iterator, List, NamedTuple, Optional, Set import bracex import certifi -import toml +import tomli from packaging.specifiers import SpecifierSet from packaging.version import Version @@ -71,7 +71,8 @@ def get_build_verbosity_extra_flags(level: int) -> List[str]: def read_python_configs(config: PlatformName) -> List[Dict[str, str]]: input_file = resources_dir / "build-platforms.toml" - loaded_file = toml.load(input_file) + with input_file.open("rb") as f: + loaded_file = tomli.load(f) results: List[Dict[str, str]] = list(loaded_file[config]["python_configurations"]) return results diff --git a/pyproject.toml b/pyproject.toml index 60ca9618..2789fd27 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,7 +58,6 @@ module = [ "setuptools", "pytest", # ignored in pre-commit to speed up check "bashlex", - "toml.encoder", # encoder missing from stub package "bracex", "importlib_resources", "nox", diff --git a/setup.cfg b/setup.cfg index c14557d8..5cc57757 100644 --- a/setup.cfg +++ b/setup.cfg @@ -35,7 +35,7 @@ install_requires = bracex certifi packaging - toml + tomli typing_extensions;python_version < '3.8' python_requires = >=3.6 include_package_data = True diff --git a/unit_test/build_ids_test.py b/unit_test/build_ids_test.py index 6aa100e3..927d110a 100644 --- a/unit_test/build_ids_test.py +++ b/unit_test/build_ids_test.py @@ -1,28 +1,32 @@ -import toml +from typing import Dict, List + +import tomli from packaging.version import Version -from cibuildwheel.extra import InlineArrayDictEncoder # noqa: E402 +from cibuildwheel.extra import Printable, dump_python_configurations from cibuildwheel.util import resources_dir def test_compare_configs(): - with open(resources_dir / "build-platforms.toml") as f: - txt = f.read() + with open(resources_dir / "build-platforms.toml") as f1: + txt = f1.read() - dict_txt = toml.loads(txt) + with open(resources_dir / "build-platforms.toml", "rb") as f2: + dict_txt = tomli.load(f2) - new_txt = toml.dumps(dict_txt, encoder=InlineArrayDictEncoder()) # type: ignore + new_txt = dump_python_configurations(dict_txt) print(new_txt) assert new_txt == txt def test_dump_with_Version(): - example = { + # MyPy doesn't understand deeply nested dicts correctly + example: Dict[str, Dict[str, List[Dict[str, Printable]]]] = { "windows": { "python_configurations": [ {"identifier": "cp27-win32", "version": Version("2.7.18"), "arch": "32"}, - {"identifier": "cp27-win_amd64", "version": Version("2.7.18"), "arch": "64"}, + {"identifier": "cp27-win_amd64", "version": "2.7.18", "arch": "64"}, ] } } @@ -35,6 +39,6 @@ python_configurations = [ ] """ - output = toml.dumps(example, encoder=InlineArrayDictEncoder()) # type: ignore + output = dump_python_configurations(example) print(output) assert output == result