A tool to rename Python wheel packages for multi-version installation.
When you need to install multiple versions of the same Python package in a single environment (e.g., for regression testing), you can use this tool to rename one version's wheel so both can coexist:
# In your test code:
import icechunk_v1 # The v1 version
import icechunk # The v2 version
# Test that v2 can read data written by v1# Use directly with uvx (recommended)
uvx spare-tire --help
# Or install globally
pip install spare-tireHere's a complete example of setting up both icechunk versions for regression testing:
# 1. Download and rename v1 in one command (specify target Python version for uvx)
uvx spare-tire download icechunk \
-i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple \
--version "<2" \
--rename icechunk_v1 \
--python-version 3.12 \
-o ./wheels/
# 2. Download v2 wheel from nightly builds
uvx spare-tire download icechunk \
-i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple \
--version ">=2.0.0.dev0" \
--python-version 3.12 \
-o ./wheels/
# 3. Create a venv and install both versions
uv venv
uv pip install ./wheels/icechunk_v1-*.whl # v1 as icechunk_v1
uv pip install ./wheels/icechunk-2*.whl # v2 as icechunk
# 4. Verify both work
uv run python -c "import icechunk_v1; print(f'v1: {icechunk_v1.__version__}')"
uv run python -c "import icechunk; print(f'v2: {icechunk.__version__}')"Optional: Inspect a wheel before renaming to verify it uses underscore-prefix extensions:
uvx spare-tire inspect ./wheels/icechunk-*.whlRename a wheel package:
spare-tire rename <wheel_path> <new_name> [-o <output_dir>]
# Examples:
spare-tire rename icechunk-1.0.0-cp312-cp312-linux_x86_64.whl icechunk_v1
spare-tire rename ./downloads/pkg.whl my_pkg_old -o ./renamed/Options:
-o, --output: Output directory (default: same as input)--no-update-imports: Don't update import statements in Python files
Download a compatible wheel from a package index:
spare-tire download <package> [-o <output_dir>] [-i <index_url>] [--version <spec>] [--rename <new_name>]
# Examples:
spare-tire download numpy -o ./wheels/
spare-tire download icechunk -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple
spare-tire download requests --version ">=2.0,<3"
spare-tire download icechunk --version "<2" -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple
# Download and rename in one command:
spare-tire download icechunk --version "<2" --rename icechunk_v1 -o ./wheels/ \
-i https://pypi.anaconda.org/scientific-python-nightly-wheels/simpleOptions:
-o, --output: Output directory (default: current directory)-i, --index-url: Package index URL (default: PyPI)--version: PEP 440 version specifier (e.g.,==1.0.0,<2,>=1.0,<2)--list: List available wheels without downloading--rename: Rename the downloaded wheel to this package name (combines download + rename)--python-version: Target Python version (e.g.,3.12). Useful withuvxto download wheels for a different Python than the one running spare-tire.
Inspect a wheel's structure before renaming:
spare-tire inspect <wheel_path> [--json]
# Example output:
# Wheel: icechunk-1.1.14-cp312-cp312-macosx_11_0_arm64.whl
# Distribution: icechunk
# Version: 1.1.14
#
# Compiled extensions (1):
# - icechunk/_icechunk_python.cpython-312-darwin.so (underscore prefix - renamable)
#
# This wheel uses underscore-prefix extensions.
# Renaming should work correctly.Start a PEP 503 proxy server that renames packages on-the-fly:
# Install with server extras
pip install spare-tire[server]
# Start proxy with CLI options
spare-tire serve \
-u https://pypi.anaconda.org/scientific-python-nightly-wheels/simple \
-r "icechunk=icechunk_v1:<2" \
--port 8000
# Or use a config file
spare-tire serve -c proxy.tomlOptions:
-c, --config: Path to TOML config file-u, --upstream: Upstream index URL (can be specified multiple times)-r, --rename: Rename rule in formatoriginal=new_name[:version_spec]--host: Host to bind to (default: 127.0.0.1)--port: Port to listen on (default: 8000)
Config file format (proxy.toml):
[proxy]
host = "127.0.0.1"
port = 8000
[[proxy.upstreams]]
url = "https://pypi.anaconda.org/scientific-python-nightly-wheels/simple/"
[renames]
icechunk = { name = "icechunk_v1", version = "<2" }Using with uv:
# Start the proxy
spare-tire serve -u https://pypi.org/simple/ -r "requests=requests_old:<2"
# In another terminal, install from the proxy
uv pip install requests_old --index-url http://127.0.0.1:8000/simple/The proxy:
- Lists virtual packages (renamed packages) at
/simple/ - Fetches the original package from upstream when requested
- Filters by version constraint if specified
- Renames the wheel on-the-fly during download
- Serves the renamed wheel to the client
- Extracts the wheel (which is a ZIP file)
- Renames the package directory (
pkg/→pkg_v1/) - Renames the
.dist-infodirectory - Updates METADATA with the new package name
- Updates imports in all Python files (
from pkg import→from pkg_v1 import) - Regenerates RECORD with new file paths and SHA256 hashes
- Repacks as a new wheel with the renamed filename
For wheels with compiled extensions (.so/.pyd files), renaming works only if the extension uses an underscore-prefix naming pattern:
| Pattern | Example | Renamable? |
|---|---|---|
_modulename.cpython-*.so |
_icechunk_python.cpython-312-darwin.so |
Yes |
modulename.cpython-*.so |
icechunk.cpython-312-darwin.so |
No |
Python's import system requires the PyInit_<name> function inside the .so file to match the filename. When you have _mymodule.cpython-*.so:
- Python looks for
PyInit__mymodule(matches!) - The parent package directory can be renamed freely
from newpkg._mymodule import ...works because the.soname is unchanged
If the extension doesn't use the underscore prefix pattern, the tool will warn you and you should rebuild from source instead.
- Compiled extensions without underscore prefix: Cannot be renamed without rebuilding
- Hardcoded package names in strings: Not automatically updated (only import statements are)
- Entry points: Updated in metadata but external scripts may need adjustment
# Clone and setup
git clone <repo>
cd spare-tire
uv sync --all-extras
# Run tests
uv run pytest
# Lint and format
uv run ruff check src tests
uv run ruff format src testsBSD-3-Clause