Skip to content
← Back to explainers

Python explainer

Build a Wheel and Install It

About 4 min to read. Allow extra time to work through examples.

Build your package the way pip delivers it, install it in a fresh virtual environment, and check which copy Python imports. Learn what a wheel, site-packages and an editable install are.

This article uses Python. You can make it your practice mode to browse related content across the site.

Your package reads an HTML template from src/mypkg/templates/report.html. The tests pass on your laptop. A teammate installs the package and gets this:

FileNotFoundError: [Errno 2] No such file or directory: '.../site-packages/mypkg/templates/report.html'

Your tests read the files in your project folder. Your teammate got what pip installed. To see what they got, build the package and install it yourself.

Build a wheel

A wheel is a .whl file, which is a zip archive of your package's files, ready to install. It is what pip downloads from PyPI. Here is a small project:

project/
  pyproject.toml
  src/
    mypkg/
      __init__.py
      templates/report.html
[build-system]
requires = ["setuptools>=64"]
build-backend = "setuptools.build_meta"

[project]
name = "mypkg"
version = "0.1.0"
requires-python = ">=3.12"

The next article explains each table. From project/, build the wheel:

python -m pip wheel . --no-deps --wheel-dir dist

This writes dist/mypkg-0.1.0-py3-none-any.whl. The file name gives the package, the version and where it runs. py3-none-any means any Python 3 on any operating system.

Look inside the wheel

A wheel is a zip file, so zipfile can list it:

import zipfile

with zipfile.ZipFile("dist/mypkg-0.1.0-py3-none-any.whl") as wheel:
    for name in wheel.namelist():
        print(name)
mypkg/__init__.py
mypkg-0.1.0.dist-info/METADATA
mypkg-0.1.0.dist-info/WHEEL
mypkg-0.1.0.dist-info/top_level.txt
mypkg-0.1.0.dist-info/RECORD

The .dist-info folder is for pip. METADATA holds the name, version and dependencies, and RECORD lists every file pip installs. The template is missing. With this configuration, setuptools packs the Python files and leaves the HTML file out. Name the extra files in pyproject.toml, rebuild, and the template is in the list:

[tool.setuptools.package-data]
mypkg = ["templates/*.html"]

Install it in a fresh virtual environment

A virtual environment is a folder with its own Python and its own site-packages, the folder where pip puts installed packages. Make one, install the project, and ask Python which file it imported:

python -m venv .venv
source .venv/bin/activate
python -m pip install .
cd ..
python -c "import mypkg; print(mypkg.__file__)"
/home/you/project/.venv/lib/python3.14/site-packages/mypkg/__init__.py

pip install . builds the same wheel and unpacks it into site-packages. On Windows, activate with .venv\Scripts\activate. The cd .. matters with a flat layout, where a mypkg folder sits next to pyproject.toml. If you ran the command from there, Python would import that folder instead of the installed copy.

An editable install points back at your folder

Reinstalling after every edit is slow. An editable install installs a link back to your project folder instead of a copy:

cd project
python -m pip install -e .
python -c "import mypkg; print(mypkg.__file__)"
/home/you/project/src/mypkg/__init__.py

Edits now take effect without reinstalling. That is also why your tests never noticed the missing template. Python read it straight from your project folder. Use an editable install while you work, and test the built wheel in a fresh environment before a release.

Common mistakes

  • Testing only the editable install. It reads your project folder, so a file missing from the wheel goes unnoticed. Install the wheel in a fresh virtual environment too, and run a test that reads each data file.
  • Checking the import from inside the project folder. With a flat layout, the current folder can win over the installed copy. The first item in this track shows how to tell which copy you got.
Check your understanding After python -m pip install -e ., mypkg.__file__ points into your project's src/ folder. Does that tell you the wheel holds every file your package needs? Show answer

No. An editable install reads from your project folder, so it shows you what is in the folder, not what is in the wheel. Build the wheel, list it with zipfile or install it in a fresh virtual environment, and run a test that reads each data file.

Going further

  • An sdist, or source distribution, is a .tar.gz of the project source. pip can build a wheel from it when no wheel fits your platform.
  • python -m build and uv build also build wheels, and they write an sdist as well.
  • The packaging tutorial in the Python Packaging User Guide walks through a whole project.

step 22 of 41

Modules, Packaging and Tooling

Up next Find the Files Missing from a Wheel