Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

Wheel Axle - Python Wheel enhancement library

Gitter Build Status Coverage Status

Wheel Axle Version Wheel Axle Python Versions

Wheel Axle Downloads Per Day Wheel Axle Downloads Per Week Wheel Axle Downloads Per Month

Problem

  1. Python wheel does not support symlinks.
  2. Python wheel does not support overwriting in a convenient way:
    • whether the distribution is pure-Python
    • distribution ABI tag
    • extend Python tag override capability

Solution

WARNING: THIS IS EXPERIMENTAL BETA SOFTWARE. THERE ARE NO WARRANTIES OF ANY KIND. USE AT YOUR OWN RISK. ADDITIONAL INCLUDED DISCLAIMERS ALSO APPLY.

Wheel-Axle (axle, bdist_axle) is a drop-in replacement/augmentation utility for wheel (bdist_wheel) that extends and builds spec-compliant wheels.

During the build, axle is able to capture and record in the Python wheel the symlinks in the following schema paths (locations):

  • purelib
  • platlib
  • scripts
  • headers
  • data

Additionally, Axle is able to customize the Python wheel tags via additional command line options.

While the generated Python wheel is fully spec-compliant, additional symlink functionality is not possible without its companion library Wheel Axle Runtime. Thus, every Python wheel generated by the bdist_axle automatically becomes dependent on wheel-axle-runtime that provides post-install logic required.

Implementation

The body of the library is as ugly and messy as distutils/setuptools are, and consists of, mainly, in hacking/overwriting various setuptools commands to detect, handle and record symlinks and their targets. Once that problem is solved, the list of symlinks is recorded in the .dist-info/symlinks.txt in the following CSV format:

  1. symlink name
  2. symlink target
  3. a boolean (0 or 1) flag indicating whether the target is a directory

NOTE: Symlinks may be relative, absolute and/or broken. Symlink targets are recorded verbatim (even when broken) and are NOT otherwise interpreted. THIS IS INTENTIONAL. Please see Wheel Axle Runtime Security Notice for additional information.

Two special startup files are also added to the distribution, and whichever one the running interpreter honors triggers the post-install logic via wheel-axle-runtime:

  • <distribution name and version>.pth, whose import line site executes up to Python 3.17
  • <distribution name and version>.start, a PEP 829 startup entry point file honored from Python 3.15 on, whose mere presence also suppresses the import line of the matching .pth

The two files deliberately call different entry points. The .start file names wheel_axle.runtime:start, which only exists in wheel-axle-runtime 0.0.12 and later, while the .pth file keeps calling wheel_axle.runtime.finalize, which every published runtime provides. Because Python 3.15 is the first version to honor a .start file, and also the first version supported by 0.0.12, the generated wheel scopes that floor with an environment marker:

Requires-Dist: wheel-axle-runtime<1.0; python_version < "3.15"
Requires-Dist: wheel-axle-runtime<1.0,>=0.0.12; python_version >= "3.15"

This keeps a wheel installable on every interpreter it otherwise supports. A py3-none-any wheel built on a recent Python still installs on Python 3.9, where the marker leaves the requirement unbounded and the newest runtime compatible with that interpreter is selected, while Python 3.15 and later are guaranteed a runtime that can resolve the .start entry point even when an older one is already installed.

NOTE: wheels built by bdist_axle before 0.0.13 only carry the .pth file. Its import line reads the fullname local of site, which the initial PEP 829 implementation renamed, and which was restored before Python 3.15.0 was released. Those wheels therefore keep working on Python 3.15 proper; only the 3.15 pre-releases carrying the rename fail, and there the failure is a NameError traceback at every interpreter startup with the symlinks left uncreated. Rebuilding with bdist_axle 0.0.14 or later is still recommended, because PEP 829 stops executing .pth import lines altogether in Python 3.18 and only a .start file works from then on. Please see the Wheel Axle Runtime compatibility notes for details.

NOTE: bdist_axle 0.0.13 requires wheel-axle-runtime>=0.0.12 unconditionally, which makes the wheels it builds uninstallable on Python 3.9. Use 0.0.14 or later.

Usage

python setup.py bdist_wheel <arguments> can be replaced with python setup.py bdist_axle <arguments>. The replacement is drop-in.

Additional functionality is available via the following options:

  --python-tag        Python implementation compatibility tag (default: 'py3')
  --root-is-pure      set to manually override whether the wheel is
                      pure (default: None)
  --abi-tag           set to override ABI tag (default: None)
  --require-libpython set to indicate the package requires libpython in the
                       exec_prefix/platlib

Using --python-tag, --root-is-pure and --abi-tag allows you to create wheels that carry platform-dependent data while otherwise containing pure-Python libraries.

PEP 517 builds

A PEP 517 frontend such as pip or build never invokes bdist_axle by name, so a project that must be installable straight from a source tree or an sdist has to install BdistAxle in place of bdist_wheel instead:

from wheel_axle.bdist_axle import BdistAxle

setup(
    ...,
    cmdclass={"bdist_wheel": BdistAxle}
)

The command line options above are then supplied through setup.cfg, in a [bdist_wheel] section, since the frontend owns the command line:

[bdist_wheel]
root_is_pure = false
require_libpython = true

With that in place pip install <source tree>, pip wheel <source tree> and build all produce a complete axle wheel:

python -m build --wheel

NOTE: build the wheel directly from the source tree, as above. python -m build with no arguments builds an sdist first and then builds the wheel from that sdist, and setuptools' sdist command resolves every symlink into a copy of its target. The wheel that comes out of the sdist therefore has an empty symlinks.txt and carries each symlink target duplicated once per link that pointed at it. This is a property of sdist itself, not of bdist_axle, and it applies equally to pip install <sdist> and to anything else installing from a source distribution. Symlinks only survive a build that reads the original working tree.

NOTE: bdist_axle before 0.0.15 crashes in prepare_metadata_for_build_wheel when installed as bdist_wheel, making pip install <source tree> and pip install <sdist> fail outright for any project that does this.

Release files for wheel-axle 0.0.15.dev20260916055353

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for wheel-axle 0.0.15.dev20260916055353
File Size Uploaded
wheel_axle-0.0.15.dev20260916055353.tar.gz 18.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for wheel-axle 0.0.15.dev20260916055353
File Interpreter ABI Platform
wheel_axle-0.0.15.dev20260916055353-py3-none-any.whl Python 3 none any Details

Total release size:30.8 kB

Release files / wheel_axle-0.0.15.dev20260916055353.tar.gz

Download URL wheel_axle-0.0.15.dev20260916055353.tar.gz
Size 18.5 kB
Tags Source
SHA-256 checksum
How to use checksums
febaf5fec92942845aba4d553224a29daa12c0fba6c34396da4af7fb9e7fa066
BLAKE2b-256 checksum
How to use checksums
f6a38caaadfc8153fab460f420db22dda2d1fd6b1af317539bb979be738561ce
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.7

Release files / wheel_axle-0.0.15.dev20260916055353-py3-none-any.whl

Download URL wheel_axle-0.0.15.dev20260916055353-py3-none-any.whl
Size 12.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8ad7435b315781759a52405347a4e14235b1ffddf25e48bc27ec8f7200eae9c0
BLAKE2b-256 checksum
How to use checksums
04e4ed44a507a24b11151b5fd5a9fcae973c28a576aa75e28864f0e5f4ec47ec
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.7

Release history Release notifications | RSS feed

0.0.15

2 release files

This release

0.0.14

2 release files

0.0.13

2 release files

0.0.12

2 release files

0.0.11

2 release files

0.0.10

2 release files

0.0.9

2 release files

0.0.8

2 release files

0.0.7

2 release files

0.0.6

2 release files

0.0.5

2 release files

0.0.4

2 release files

0.0.3

2 release files

0.0.2

2 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page