Publish Wheels to PyPI for Faster Reliable Installs
Wheels let pip install pre-built packages without a compiler. See how to build, upload and verify a wheel for a small C extension on PyPI.
07 Dec 2025, 09:15 UTC

The problem: installs that require a build step
Running pip install on a clean machine can fall back to a source distribution. That means pip downloads an sdist, unpacks it, and tries to build the package. For packages with C extensions this requires a compiler, build dependencies, and time. When the build environment is missing, the install fails.
Takeaway: ship a wheel and skip the build
A wheel is a pre-built binary distribution defined by PEP 427. When a matching wheel is available on PyPI, pip prefers it over an sdist. Installation becomes unpacking files into site-packages, with no compiler needed on the consumer side.
What a wheel contains
A wheel is a ZIP archive with a standardized filename that encodes Python version, ABI and platform. Inside is a .dist-info directory with metadata and the package files. For binary packages this includes compiled extensions such as .so on Linux or .pyd on Windows, ready to use.
Universal wheels use the tag py3-none-any and work across Python versions and operating systems. Platform-specific wheels such as cp311-win_amd64 encode interpreter ABI and OS, which is required for binary extensions.
Worked example: build and upload a wheel
The following steps show how a publisher can create and publish a wheel. Run them in a project directory with write permission.
- Create a minimal build configuration. A pyproject.toml with setuptools build backend is sufficient:
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "example-ext"
version = "0.1.0"
requires-python = ">&=3.8"With a package layout containing the extension sources, build both sdist and wheel in an isolated environment:
python -m pip install --upgrade build twine
python -m buildThe command creates dist/example-ext-0.1.0.tar.gz and a wheel such as dist/example-ext-0.1.0-cp311-cp311-linux_x86_64.whl. The exact tag depends on the interpreter and platform used for the build.
Upload to a test index first. Replace USERNAME and PASSWORD with PyPI credentials and run from the project root:
python -m twine upload --repository testpypi dist/*Risk: uploading to pypi.org is irreversible for a given version. Use test.pypi.org for validation and run twine check before publishing.
Install in a fresh virtual environment to verify selection:
python -m venv /tmp/testenv
/tmp/testenv/bin/python -m venv /tmp/testenv
/tmp/testenv/bin/pip install --no-cache-dir --index-url https://test.pypi.org/simple/ example-ext==0.1.0Check that pip reports a wheel download and does not report building the package. Inspect the installed wheel:
python -m wheel unpack /tmp/testenv/lib/python*/site-packages/example-ext-*.whlVerify the presence of the .dist-info directory and a compiled extension file. Importing the package in Python should succeed without triggering a build.
Trade-offs and limitations
Wheels are platform-specific unless marked universal. A wheel built on one OS or Python version will not install on a mismatched environment and can lead to an invalid wheel filename error.
Binary wheels for Linux should be built against the manylinux standard to be broadly compatible. Wheels built against a local system library may fail at runtime with missing shared-library errors on user machines.
Pure-Python projects can publish a single universal wheel py3-none-any. Packages with compiled code need separate wheels per platform, increasing publishing complexity.
Closing
If your package includes compiled code, publishing a correctly tagged wheel gives consumers faster and more reliable installs. Build in a clean environment, tag for the intended platforms, verify with a test install and wheel inspection, and only then publish to the main index.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.