Build Python wheels on CI with minimal configuration.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Cibuildwheel provides various utility functions for string formatting, command preparation, version parsing, and other common operations.
Safe string formatting with path support.
def format_safe(template: str, **kwargs: str | os.PathLike[str]) -> str:
"""
Safely format a template string with keyword arguments.
Args:
template: Template string with {key} placeholders
**kwargs: Keyword arguments for substitution
Returns:
Formatted string with substitutions applied
Raises:
KeyError: If template contains undefined placeholders
"""Prepare shell commands with safe argument substitution.
def prepare_command(command: str, **kwargs: PathOrStr) -> str:
"""
Prepare a shell command with safe argument substitution.
Args:
command: Command template with {key} placeholders
**kwargs: Arguments for substitution (strings or paths)
Returns:
Command string with arguments safely substituted
"""Convert string values to boolean with flexible input support.
def strtobool(val: str) -> bool:
"""
Convert string representation of truth to boolean.
Args:
val: String value to convert
Returns:
Boolean representation of the input
Raises:
ValueError: If string is not a recognized boolean value
Accepts: 'y', 'yes', 't', 'true', 'on', '1' (case-insensitive) for True
'n', 'no', 'f', 'false', 'off', '0' (case-insensitive) for False
"""Utilities for unwrapping and reformatting text.
def unwrap(text: str) -> str:
"""
Unwrap text by removing line breaks within paragraphs.
Args:
text: Input text with possible line breaks
Returns:
Text with line breaks removed within paragraphs
"""
def unwrap_preserving_paragraphs(text: str) -> str:
"""
Unwrap text while preserving paragraph breaks.
Args:
text: Input text with paragraphs separated by blank lines
Returns:
Text with line breaks removed within paragraphs but paragraph breaks preserved
"""Parse configuration strings containing key-value pairs.
def parse_key_value_string(
key_value_string: str,
positional_arg_names: Sequence[str] | None = None,
kw_arg_names: Sequence[str] | None = None,
) -> dict[str, list[str]]:
"""
Parse a string containing key-value pairs and positional arguments.
Args:
key_value_string: String to parse (e.g., "pos1 pos2 key1=val1 key2=val2")
positional_arg_names: Names for positional arguments
kw_arg_names: Valid keyword argument names
Returns:
Dictionary mapping argument names to lists of values
Raises:
ValueError: If parsing fails or invalid arguments provided
"""Flexible version comparison and parsing.
@dataclasses.dataclass(order=True)
class FlexibleVersion:
"""
Version class that handles various version formats flexibly.
Supports:
- Semantic versioning (1.2.3)
- Python version format (3.11.0)
- Pre-release versions (1.0.0a1, 1.0.0rc1)
- Development versions (1.0.0.dev0)
- Epoch versions (1!1.0.0)
"""
def __init__(self, version: str) -> None:
"""
Initialize from version string.
Args:
version: Version string to parse
"""
def __str__(self) -> str:
"""Return string representation of version."""
def __eq__(self, other: object) -> bool:
"""Check version equality."""
def __lt__(self, other: FlexibleVersion) -> bool:
"""Compare versions for ordering."""from cibuildwheel.util.helpers import format_safe
# Basic formatting
template = "Building {package} version {version}"
result = format_safe(template, package="my-package", version="1.0.0")
# Result: "Building my-package version 1.0.0"
# Path formatting
template = "Output to {output_dir}/wheels"
result = format_safe(template, output_dir=Path("/tmp/build"))
# Result: "Output to /tmp/build/wheels"from cibuildwheel.util.helpers import prepare_command
from pathlib import Path
# Prepare test command
command = "pytest {project}/tests --output={output}"
prepared = prepare_command(
command,
project=Path("/src/mypackage"),
output=Path("/tmp/test-results.xml")
)
# Result: "pytest /src/mypackage/tests --output=/tmp/test-results.xml"
# Command with shell escaping
command = "echo {message}"
prepared = prepare_command(command, message="Hello World with spaces")
# Result: "echo 'Hello World with spaces'"from cibuildwheel.util.helpers import strtobool
# Convert various string representations
strtobool("yes") # True
strtobool("true") # True
strtobool("1") # True
strtobool("on") # True
strtobool("no") # False
strtobool("false") # False
strtobool("0") # False
strtobool("off") # False
# Case insensitive
strtobool("TRUE") # True
strtobool("False") # False
# Error on invalid input
try:
strtobool("maybe") # ValueError
except ValueError:
passfrom cibuildwheel.util.helpers import unwrap, unwrap_preserving_paragraphs
# Simple unwrapping
text = """This is a long line
that was wrapped
for display."""
unwrapped = unwrap(text)
# Result: "This is a long line that was wrapped for display."
# Preserving paragraphs
text = """First paragraph
with wrapped lines.
Second paragraph
also wrapped."""
unwrapped = unwrap_preserving_paragraphs(text)
# Result: "First paragraph with wrapped lines.\n\nSecond paragraph also wrapped."from cibuildwheel.util.helpers import parse_key_value_string
# Parse mixed positional and keyword arguments
config = "arg1 arg2 key1=value1 key2=value2"
parsed = parse_key_value_string(
config,
positional_arg_names=["first", "second"],
kw_arg_names=["key1", "key2", "key3"]
)
# Result: {
# "first": ["arg1"],
# "second": ["arg2"],
# "key1": ["value1"],
# "key2": ["value2"]
# }
# Multiple values for same key
config = "key1=val1 key1=val2 key2=single"
parsed = parse_key_value_string(config)
# Result: {
# "key1": ["val1", "val2"],
# "key2": ["single"]
# }from cibuildwheel.util.helpers import FlexibleVersion
# Create version objects
v1 = FlexibleVersion("1.2.3")
v2 = FlexibleVersion("1.2.4")
v3 = FlexibleVersion("1.2.3a1")
v4 = FlexibleVersion("2.0.0")
# Comparison operations
print(v1 < v2) # True
print(v1 > v3) # True (release > prerelease)
print(v2 < v4) # True
# Sorting versions
versions = [v4, v1, v3, v2]
sorted_versions = sorted(versions)
# Result: [v3, v1, v2, v4] (1.2.3a1, 1.2.3, 1.2.4, 2.0.0)
# String representation
print(str(v1)) # "1.2.3"
print(str(v3)) # "1.2.3a1"from cibuildwheel.util.helpers import format_safe
# Build complex command templates
template = """
cd {project_dir} && \
python -m pip install {build_deps} && \
python -m build --outdir {wheel_dir}
"""
command = format_safe(
template,
project_dir="/src/mypackage",
build_deps="wheel setuptools",
wheel_dir="/tmp/wheels"
).strip()from cibuildwheel.util.helpers import parse_key_value_string
# Parse build frontend configuration
frontend_config = "build --installer uv --outdir {wheel_dir}"
parsed = parse_key_value_string(
frontend_config,
positional_arg_names=["tool"],
kw_arg_names=["installer", "outdir"]
)
tool = parsed["tool"][0] # "build"
installer = parsed.get("installer", ["pip"])[0] # "uv"
outdir = parsed.get("outdir", ["{wheel_dir}"])[0] # "{wheel_dir}"from cibuildwheel.util.helpers import FlexibleVersion
def check_python_compatibility(python_version: str, min_version: str = "3.8"):
"""Check if Python version meets minimum requirement."""
current = FlexibleVersion(python_version)
minimum = FlexibleVersion(min_version)
return current >= minimum
# Usage
check_python_compatibility("3.11.0", "3.8") # True
check_python_compatibility("3.7.0", "3.8") # Falsefrom cibuildwheel.util.helpers import strtobool
import os
def get_bool_env(var_name: str, default: bool = False) -> bool:
"""Get boolean environment variable with default."""
value = os.environ.get(var_name)
if value is None:
return default
return strtobool(value)
# Usage
debug_enabled = get_bool_env("CIBW_DEBUG", False)
skip_tests = get_bool_env("CIBW_SKIP_TESTS", False)from cibuildwheel.util.helpers import unwrap_preserving_paragraphs
def process_multiline_config(config_text: str) -> str:
"""Process multiline configuration text."""
# Remove extra whitespace and unwrap lines
processed = unwrap_preserving_paragraphs(config_text.strip())
# Additional processing
lines = processed.split('\n')
cleaned_lines = [line.strip() for line in lines if line.strip()]
return '\n'.join(cleaned_lines)
# Usage with configuration files
config = """
build = cp39-* cp310-*
cp311-*
skip = *-win32
*-linux_i686
"""
processed = process_multiline_config(config)
# Result: "build = cp39-* cp310-* cp311-*\n\nskip = *-win32 *-linux_i686"These utilities are used throughout cibuildwheel for:
The utilities provide a consistent interface for common operations while handling edge cases and platform differences internally.
Install with Tessl CLI
npx tessl i tessl/pypi-cibuildwheel