chore: pattern matching (#2434)

* chore: use match for stringify_setting

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

* chore: more match statements

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

---------

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
Co-authored-by: Joe Rickerby <joerick@mac.com>
This commit is contained in:
Henry Schreiner
2025-07-08 08:59:20 -04:00
committed by GitHub
co-authored by Joe Rickerby
parent 46ef6044e2
commit 834ea40510
7 changed files with 283 additions and 277 deletions
+36 -33
View File
@@ -95,43 +95,46 @@ def _check_engine_version(engine: OCIContainerEngineConfig) -> None:
try: try:
version_string = call(engine.name, "version", "-f", "{{json .}}", capture_stdout=True) version_string = call(engine.name, "version", "-f", "{{json .}}", capture_stdout=True)
version_info = json.loads(version_string.strip()) version_info = json.loads(version_string.strip())
if engine.name == "docker": match engine.name:
client_api_version = FlexibleVersion(version_info["Client"]["ApiVersion"]) case "docker":
server_api_version = FlexibleVersion(version_info["Server"]["ApiVersion"]) client_api_version = FlexibleVersion(version_info["Client"]["ApiVersion"])
# --platform support was introduced in 1.32 as experimental, 1.41 removed the experimental flag server_api_version = FlexibleVersion(version_info["Server"]["ApiVersion"])
version = min(client_api_version, server_api_version) # --platform support was introduced in 1.32 as experimental, 1.41 removed the experimental flag
minimum_version = FlexibleVersion("1.41") version = min(client_api_version, server_api_version)
minimum_version_str = "20.10.0" # docker version minimum_version = FlexibleVersion("1.41")
error_msg = textwrap.dedent( minimum_version_str = "20.10.0" # docker version
f""" error_msg = textwrap.dedent(
Build failed because {engine.name} is too old. f"""
Build failed because {engine.name} is too old.
cibuildwheel requires {engine.name}>={minimum_version_str} running API version {minimum_version}. cibuildwheel requires {engine.name}>={minimum_version_str} running API version {minimum_version}.
The API version found by cibuildwheel is {version}. The API version found by cibuildwheel is {version}.
""" """
) )
elif engine.name == "podman": case "podman":
# podman uses the same version string for "Version" & "ApiVersion" # podman uses the same version string for "Version" & "ApiVersion"
client_version = FlexibleVersion(version_info["Client"]["Version"]) client_version = FlexibleVersion(version_info["Client"]["Version"])
if "Server" in version_info: if "Server" in version_info:
server_version = FlexibleVersion(version_info["Server"]["Version"]) server_version = FlexibleVersion(version_info["Server"]["Version"])
else: else:
server_version = client_version server_version = client_version
# --platform support was introduced in v3 # --platform support was introduced in v3
version = min(client_version, server_version) version = min(client_version, server_version)
minimum_version = FlexibleVersion("3") minimum_version = FlexibleVersion("3")
error_msg = textwrap.dedent( error_msg = textwrap.dedent(
f""" f"""
Build failed because {engine.name} is too old. Build failed because {engine.name} is too old.
cibuildwheel requires {engine.name}>={minimum_version}.
The version found by cibuildwheel is {version}.
"""
)
case _:
assert_never(engine.name)
cibuildwheel requires {engine.name}>={minimum_version}.
The version found by cibuildwheel is {version}.
"""
)
else:
assert_never(engine.name)
if version < minimum_version: if version < minimum_version:
raise OCIEngineTooOldError(error_msg) from None raise OCIEngineTooOldError(error_msg) from None
except (subprocess.CalledProcessError, KeyError, ValueError) as e: except (subprocess.CalledProcessError, KeyError, ValueError) as e:
msg = f"Build failed because {engine.name} is too old or is not working properly." msg = f"Build failed because {engine.name} is too old or is not working properly."
raise OCIEngineTooOldError(msg) from e raise OCIEngineTooOldError(msg) from e
+34 -28
View File
@@ -343,40 +343,46 @@ def _apply_inherit_rule(
msg = f"Don't know how to merge {before!r} and {after!r} with {rule}" msg = f"Don't know how to merge {before!r} and {after!r} with {rule}"
raise OptionsReaderError(msg) raise OptionsReaderError(msg)
if rule == InheritRule.APPEND: match rule:
return option_format.merge_values(before, after) case InheritRule.APPEND:
if rule == InheritRule.PREPEND: return option_format.merge_values(before, after)
return option_format.merge_values(after, before) case InheritRule.PREPEND:
return option_format.merge_values(after, before)
assert_never(rule) case _:
assert_never(rule)
def _stringify_setting( def _stringify_setting(
setting: SettingValue, setting: SettingValue,
option_format: OptionFormat | None, option_format: OptionFormat | None,
) -> str: ) -> str:
if isinstance(setting, Mapping): match setting:
try: case {}:
if option_format is None: assert isinstance(setting, Mapping) # MyPy 1.15 doesn't narrow this for us
raise OptionFormat.NotSupported try:
return option_format.format_table(setting) if option_format is None:
except OptionFormat.NotSupported: raise OptionFormat.NotSupported
msg = f"Error converting {setting!r} to a string: this setting doesn't accept a table" return option_format.format_table(setting)
raise OptionsReaderError(msg) from None except OptionFormat.NotSupported:
msg = (
if not isinstance(setting, str) and isinstance(setting, Sequence): f"Error converting {setting!r} to a string: this setting doesn't accept a table"
try: )
if option_format is None: raise OptionsReaderError(msg) from None
raise OptionFormat.NotSupported case bool() | int():
return option_format.format_list(setting) return str(setting)
except OptionFormat.NotSupported: case [*_]:
msg = f"Error converting {setting!r} to a string: this setting doesn't accept a list" try:
raise OptionsReaderError(msg) from None if option_format is None:
raise OptionFormat.NotSupported
if isinstance(setting, bool | int): return option_format.format_list(setting)
return str(setting) except OptionFormat.NotSupported:
msg = (
return setting f"Error converting {setting!r} to a string: this setting doesn't accept a list"
)
raise OptionsReaderError(msg) from None
case _:
assert isinstance(setting, str) # MyPy 1.15 doesn't narrow this for us
return setting
class OptionsReader: class OptionsReader:
+44 -42
View File
@@ -387,20 +387,21 @@ def setup_python(
env.setdefault("IPHONEOS_DEPLOYMENT_TARGET", "13.0") env.setdefault("IPHONEOS_DEPLOYMENT_TARGET", "13.0")
log.step("Installing build tools...") log.step("Installing build tools...")
if build_frontend == "pip": match build_frontend:
# No additional build tools required case "pip":
pass # No additional build tools required
elif build_frontend == "build": pass
call( case "build":
"pip", call(
"install", "pip",
"--upgrade", "install",
"build[virtualenv]", "--upgrade",
*constraint_flags(dependency_constraint), "build[virtualenv]",
env=env, *constraint_flags(dependency_constraint),
) env=env,
else: )
assert_never(build_frontend) case _:
assert_never(build_frontend)
return target_install_path, env return target_install_path, env
@@ -494,34 +495,35 @@ def build(options: Options, tmp_path: Path) -> None:
if constraints_path: if constraints_path:
combine_constraints(build_env, constraints_path, None) combine_constraints(build_env, constraints_path, None)
if build_frontend.name == "pip": match build_frontend.name:
# Path.resolve() is needed. Without it pip wheel may try to case "pip":
# fetch package from pypi.org. See # Path.resolve() is needed. Without it pip wheel may try to
# https://github.com/pypa/cibuildwheel/pull/369 # fetch package from pypi.org. See
call( # https://github.com/pypa/cibuildwheel/pull/369
"python", call(
"-m", "python",
"pip", "-m",
"wheel", "pip",
build_options.package_dir.resolve(), "wheel",
f"--wheel-dir={built_wheel_dir}", build_options.package_dir.resolve(),
"--no-deps", f"--wheel-dir={built_wheel_dir}",
*extra_flags, "--no-deps",
env=build_env, *extra_flags,
) env=build_env,
elif build_frontend.name == "build": )
call( case "build":
"python", call(
"-m", "python",
"build", "-m",
build_options.package_dir, "build",
"--wheel", build_options.package_dir,
f"--outdir={built_wheel_dir}", "--wheel",
*extra_flags, f"--outdir={built_wheel_dir}",
env=build_env, *extra_flags,
) env=build_env,
else: )
assert_never(build_frontend) case _:
assert_never(build_frontend)
test_wheel = built_wheel = next(built_wheel_dir.glob("*.whl")) test_wheel = built_wheel = next(built_wheel_dir.glob("*.whl"))
+32 -31
View File
@@ -275,37 +275,38 @@ def build_in_container(
build_frontend, build_options.build_verbosity, build_options.config_settings build_frontend, build_options.build_verbosity, build_options.config_settings
) )
if build_frontend.name == "pip": match build_frontend.name:
container.call( case "pip":
[ container.call(
"python", [
"-m", "python",
"pip", "-m",
"wheel", "pip",
container_package_dir, "wheel",
f"--wheel-dir={built_wheel_dir}", container_package_dir,
"--no-deps", f"--wheel-dir={built_wheel_dir}",
*extra_flags, "--no-deps",
], *extra_flags,
env=env, ],
) env=env,
elif build_frontend.name == "build" or build_frontend.name == "build[uv]": )
if use_uv and "--no-isolation" not in extra_flags and "-n" not in extra_flags: case "build" | "build[uv]":
extra_flags += ["--installer=uv"] if use_uv and "--no-isolation" not in extra_flags and "-n" not in extra_flags:
container.call( extra_flags += ["--installer=uv"]
[ container.call(
"python", [
"-m", "python",
"build", "-m",
container_package_dir, "build",
"--wheel", container_package_dir,
f"--outdir={built_wheel_dir}", "--wheel",
*extra_flags, f"--outdir={built_wheel_dir}",
], *extra_flags,
env=env, ],
) env=env,
else: )
assert_never(build_frontend) case _:
assert_never(build_frontend)
built_wheel = container.glob(built_wheel_dir, "*.whl")[0] built_wheel = container.glob(built_wheel_dir, "*.whl")[0]
+68 -62
View File
@@ -345,39 +345,40 @@ def setup_python(
env.setdefault("SDKROOT", arm64_compatible_sdks[0]) env.setdefault("SDKROOT", arm64_compatible_sdks[0])
log.step("Installing build tools...") log.step("Installing build tools...")
if build_frontend == "pip": match build_frontend:
call( case "pip":
"pip", call(
"install", "pip",
"--upgrade", "install",
"delocate", "--upgrade",
*constraint_flags(dependency_constraint), "delocate",
env=env, *constraint_flags(dependency_constraint),
) env=env,
elif build_frontend == "build": )
call( case "build":
"pip", call(
"install", "pip",
"--upgrade", "install",
"delocate", "--upgrade",
"build[virtualenv]", "delocate",
*constraint_flags(dependency_constraint), "build[virtualenv]",
env=env, *constraint_flags(dependency_constraint),
) env=env,
elif build_frontend == "build[uv]": )
assert uv_path is not None case "build[uv]":
call( assert uv_path is not None
uv_path, call(
"pip", uv_path,
"install", "pip",
"--upgrade", "install",
"delocate", "--upgrade",
"build[virtualenv, uv]", "delocate",
*constraint_flags(dependency_constraint), "build[virtualenv, uv]",
env=env, *constraint_flags(dependency_constraint),
) env=env,
else: )
assert_never(build_frontend) case _:
assert_never(build_frontend)
return base_python, env return base_python, env
@@ -467,35 +468,40 @@ def build(options: Options, tmp_path: Path) -> None:
build_env, constraints_path, identifier_tmp_dir if use_uv else None build_env, constraints_path, identifier_tmp_dir if use_uv else None
) )
if build_frontend.name == "pip": match build_frontend.name:
# Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org case "pip":
# see https://github.com/pypa/cibuildwheel/pull/369 # Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org
call( # see https://github.com/pypa/cibuildwheel/pull/369
"python", call(
"-m", "python",
"pip", "-m",
"wheel", "pip",
build_options.package_dir.resolve(), "wheel",
f"--wheel-dir={built_wheel_dir}", build_options.package_dir.resolve(),
"--no-deps", f"--wheel-dir={built_wheel_dir}",
*extra_flags, "--no-deps",
env=build_env, *extra_flags,
) env=build_env,
elif build_frontend.name == "build" or build_frontend.name == "build[uv]": )
if use_uv and "--no-isolation" not in extra_flags and "-n" not in extra_flags: case "build" | "build[uv]":
extra_flags.append("--installer=uv") if (
call( use_uv
"python", and "--no-isolation" not in extra_flags
"-m", and "-n" not in extra_flags
"build", ):
build_options.package_dir, extra_flags.append("--installer=uv")
"--wheel", call(
f"--outdir={built_wheel_dir}", "python",
*extra_flags, "-m",
env=build_env, "build",
) build_options.package_dir,
else: "--wheel",
assert_never(build_frontend) f"--outdir={built_wheel_dir}",
*extra_flags,
env=build_env,
)
case _:
assert_never(build_frontend)
built_wheel = next(built_wheel_dir.glob("*.whl")) built_wheel = next(built_wheel_dir.glob("*.whl"))
+55 -49
View File
@@ -305,26 +305,27 @@ def setup_python(
call("pip", "--version", env=env) call("pip", "--version", env=env)
log.step("Installing build tools...") log.step("Installing build tools...")
if build_frontend == "build": match build_frontend:
call( case "build":
"pip", call(
"install", "pip",
"--upgrade", "install",
"build[virtualenv]", "--upgrade",
*constraint_flags(dependency_constraint), "build[virtualenv]",
env=env, *constraint_flags(dependency_constraint),
) env=env,
elif build_frontend == "build[uv]": )
assert uv_path is not None case "build[uv]":
call( assert uv_path is not None
uv_path, call(
"pip", uv_path,
"install", "pip",
"--upgrade", "install",
"build[virtualenv]", "--upgrade",
*constraint_flags(dependency_constraint), "build[virtualenv]",
env=env, *constraint_flags(dependency_constraint),
) env=env,
)
if python_libs_base: if python_libs_base:
# Set up the environment for various backends to enable cross-compilation # Set up the environment for various backends to enable cross-compilation
@@ -467,36 +468,41 @@ def build(options: Options, tmp_path: Path) -> None:
if constraints_path: if constraints_path:
combine_constraints(build_env, constraints_path, identifier_tmp_dir) combine_constraints(build_env, constraints_path, identifier_tmp_dir)
if build_frontend.name == "pip": match build_frontend.name:
# Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org case "pip":
# see https://github.com/pypa/cibuildwheel/pull/369 # Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org
call( # see https://github.com/pypa/cibuildwheel/pull/369
"python", call(
"-m", "python",
"pip", "-m",
"wheel", "pip",
options.globals.package_dir.resolve(), "wheel",
f"--wheel-dir={built_wheel_dir}", options.globals.package_dir.resolve(),
"--no-deps", f"--wheel-dir={built_wheel_dir}",
*extra_flags, "--no-deps",
env=build_env, *extra_flags,
) env=build_env,
elif build_frontend.name == "build" or build_frontend.name == "build[uv]": )
if use_uv and "--no-isolation" not in extra_flags and "-n" not in extra_flags: case "build" | "build[uv]":
extra_flags.append("--installer=uv") if (
use_uv
and "--no-isolation" not in extra_flags
and "-n" not in extra_flags
):
extra_flags.append("--installer=uv")
call( call(
"python", "python",
"-m", "-m",
"build", "build",
build_options.package_dir, build_options.package_dir,
"--wheel", "--wheel",
f"--outdir={built_wheel_dir}", f"--outdir={built_wheel_dir}",
*extra_flags, *extra_flags,
env=build_env, env=build_env,
) )
else: case _:
assert_never(build_frontend) assert_never(build_frontend)
built_wheel = next(built_wheel_dir.glob("*.whl")) built_wheel = next(built_wheel_dir.glob("*.whl"))
+14 -32
View File
@@ -14,32 +14,16 @@ def get_parent(node: ast.AST | None, depth: int = 1) -> ast.AST | None:
def is_main(parent: ast.AST | None) -> bool: def is_main(parent: ast.AST | None) -> bool:
if parent is None: match parent:
return False case ast.If(test=ast.Compare(left=left, ops=[ast.Eq()], comparators=[comp])):
values = {left, comp}
# This would be much nicer with 3.10's pattern matching! mains = {x for x in values if isinstance(x, ast.Constant) and x.value == "__main__"}
if not isinstance(parent, ast.If): if len(mains) != 1:
return False return False
if not isinstance(parent.test, ast.Compare): consts = {x for x in values if isinstance(x, ast.Name) and x.id == "__name__"}
return False return len(consts) == 1
case _:
try: return False
(op,) = parent.test.ops
(comp,) = parent.test.comparators
except ValueError:
return False
if not isinstance(op, ast.Eq):
return False
values = {comp, parent.test.left}
mains = {x for x in values if isinstance(x, ast.Constant) and x.value == "__main__"}
if len(mains) != 1:
return False
consts = {x for x in values if isinstance(x, ast.Name) and x.id == "__name__"}
return len(consts) == 1
class Analyzer(ast.NodeVisitor): class Analyzer(ast.NodeVisitor):
@@ -65,12 +49,10 @@ class Analyzer(ast.NodeVisitor):
parent is not None and get_parent(parent) is None and is_main(get_parent(node, 3)) parent is not None and get_parent(parent) is None and is_main(get_parent(node, 3))
) )
if ( match node:
node.arg == "python_requires" case ast.keyword(arg="python_requires", value=ast.Constant(value=version)):
and isinstance(node.value, ast.Constant) if unnested or name_main_unnested:
and (unnested or name_main_unnested) self.requires_python = version
):
self.requires_python = node.value.value
def setup_py_python_requires(content: str) -> str | None: def setup_py_python_requires(content: str) -> str | None: