Command line tool for manipulating wheel files
Modify wheel filename tags for Python version, ABI, and platform compatibility without rebuilding the package. Enables retargeting wheels for different Python versions or platforms when the code is compatible.
Main function for modifying wheel tags and creating new wheel files with updated compatibility information.
def tags(
wheel: str,
python_tags: str | None = None,
abi_tags: str | None = None,
platform_tags: str | None = None,
build_tag: str | None = None,
remove: bool = False,
) -> str:
"""
Modify wheel tags and create new wheel file.
Parameters:
- wheel: Path to input wheel file
- python_tags: Python version tags (e.g., "py38.py39")
- abi_tags: ABI tags (e.g., "cp38.cp39")
- platform_tags: Platform tags (e.g., "linux_x86_64.win_amd64")
- build_tag: Build tag (must start with digit, no dashes)
- remove: Delete original wheel file after creating new one
Returns:
Filename of newly created wheel
Tag modification syntax:
- Replace: "py38.py39" replaces all Python tags
- Append: "+py310" adds py310 to existing tags
- Remove: "-py37" removes py37 from existing tags
- Multiple: Dot-separated values for multiple tags
Raises:
- AssertionError: If internal WHEEL tags don't match filename
- WheelError: For invalid wheel files or format errors
"""Helper functions for computing and manipulating tag sets.
def _compute_tags(original_tags: Iterable[str], new_tags: str | None) -> set[str]:
"""
Compute final tag set based on modification rules.
Parameters:
- original_tags: Current tags from wheel
- new_tags: Tag modification string (None for no change)
Returns:
Final set of tags after applying modifications
Modification rules:
- None or empty: No change (returns original_tags)
- Starting with '+': Append tags (union operation)
- Starting with '-': Remove tags (difference operation)
- Other: Replace tags completely
- Dot-separated: Multiple tags in single string
"""The tags function performs comprehensive validation to ensure wheel integrity:
from wheel._commands.tags import tags
# Replace Python version tags
new_wheel = tags(
'package-1.0-py3-none-any.whl',
python_tags='py38.py39.py310'
)
# Result: package-1.0-py38.py39.py310-none-any.whl
# Add platform support
new_wheel = tags(
'package-1.0-py3-none-any.whl',
platform_tags='linux_x86_64.macosx_10_9_x86_64'
)
# Result: package-1.0-py3-none-linux_x86_64.macosx_10_9_x86_64.whl
# Set ABI tags for compiled extensions
new_wheel = tags(
'package-1.0-py38-abi3-linux_x86_64.whl',
abi_tags='cp38.cp39.cp310'
)
# Result: package-1.0-py38-cp38.cp39.cp310-linux_x86_64.whl# Append Python version
new_wheel = tags(
'package-1.0-py38.py39-none-any.whl',
python_tags='+py310'
)
# Result: package-1.0-py38.py39.py310-none-any.whl
# Remove specific version
new_wheel = tags(
'package-1.0-py37.py38.py39-none-any.whl',
python_tags='-py37'
)
# Result: package-1.0-py38.py39-none-any.whl
# Multiple operations (applied left to right)
new_wheel = tags(
'package-1.0-py37.py38-none-any.whl',
python_tags='-py37.+py39.+py310'
)
# Result: package-1.0-py38.py39.py310-none-any.whl# Add build tag
new_wheel = tags(
'package-1.0-py3-none-any.whl',
build_tag='20231201'
)
# Result: package-1.0-20231201-py3-none-any.whl
# Remove build tag
new_wheel = tags(
'package-1.0-20231201-py3-none-any.whl',
build_tag=''
)
# Result: package-1.0-py3-none-any.whl# Keep original file (default)
new_wheel = tags('package-1.0-py3-none-any.whl', python_tags='py39')
# Creates: package-1.0-py39-none-any.whl
# Keeps: package-1.0-py3-none-any.whl
# Remove original file
new_wheel = tags(
'package-1.0-py3-none-any.whl',
python_tags='py39',
remove=True
)
# Creates: package-1.0-py39-none-any.whl
# Deletes: package-1.0-py3-none-any.whl# Multiple tag types simultaneously
new_wheel = tags(
'package-1.0-py3-none-any.whl',
python_tags='py38.py39.py310',
abi_tags='cp38.cp39.cp310',
platform_tags='linux_x86_64.win_amd64',
build_tag='20231201'
)
# Result: package-1.0-20231201-py38.py39.py310-cp38.cp39.cp310-linux_x86_64.win_amd64.whlfrom wheel._commands.tags import tags, _compute_tags
from wheel.wheelfile import WheelFile
# Analyze current tags
with WheelFile('package-1.0-py3-none-any.whl', 'r') as wf:
parsed = wf.parsed_filename
current_python = parsed.group('pyver').split('.')
current_abi = parsed.group('abi').split('.')
current_platform = parsed.group('plat').split('.')
print(f"Current Python tags: {current_python}")
print(f"Current ABI tags: {current_abi}")
print(f"Current platform tags: {current_platform}")
# Compute new tag set
new_python_tags = _compute_tags(current_python, '+py311')
print(f"New Python tags: {sorted(new_python_tags)}")
# Apply changes
new_wheel = tags(
'package-1.0-py3-none-any.whl',
python_tags='.'.join(sorted(new_python_tags))
)from wheel._commands.tags import tags
from wheel.wheelfile import WheelError
try:
# This will raise AssertionError if tags are inconsistent
new_wheel = tags('corrupted-wheel.whl', python_tags='py39')
except AssertionError as e:
print(f"Wheel integrity error: {e}")
except WheelError as e:
print(f"Wheel format error: {e}")# Create platform-specific variants from universal wheel
platforms = ['linux_x86_64', 'macosx_10_9_x86_64', 'win_amd64']
for platform in platforms:
platform_wheel = tags(
'package-1.0-py3-none-any.whl',
platform_tags=platform
)
print(f"Created: {platform_wheel}")# Update wheel for newer Python versions
python_versions = ['py39', 'py310', 'py311']
updated_wheel = tags(
'package-1.0-py38-none-any.whl',
python_tags='.'.join(python_versions)
)
print(f"Updated wheel: {updated_wheel}")# Convert from specific ABI to abi3 (stable ABI)
stable_wheel = tags(
'package-1.0-py38-cp38-linux_x86_64.whl',
abi_tags='abi3'
)
print(f"Stable ABI wheel: {stable_wheel}")from collections.abc import Iterable
from typing import Literal
# No additional types beyond function signatures
# Uses standard library types and wheel.wheelfile.WheelFileInstall with Tessl CLI
npx tessl i tessl/pypi-wheel