pip install package feels instant specifically because of a specific artifact format ā the wheel ā designed to avoid running any code at install time. This lesson covers what a build actually produces, why wheels replaced running setup.py directly, and the package-discovery configuration that has to be correct for a build to include the right files.
1What a Build Actually Produces: Wheel and Source Distribution
python -m build (or the equivalent uv build/poetry build), run against a project with a correctly-configured pyproject.toml, invokes the declared build backend (from [build-system], covered in the pyproject.toml lesson) to produce two distinct artifacts: a wheel (.whl) and a source distribution (sdist, .tar.gz). These serve genuinely different purposes, not merely two copies of the same thing in different formats.
The wheel is a pre-built, install-ready artifact ā its internal file layout already matches exactly what needs to end up in a user's environment, with metadata pre-computed. Installing a wheel is, mechanically, close to 'extract this archive's files into the right locations' ā no compilation, no running arbitrary setup code, nothing beyond file extraction and copying (plus metadata registration). This is precisely why pip install of a package with a published wheel feels close to instantaneous.
The sdist contains the actual source code in a more raw form, plus enough metadata for a build backend to build a wheel *from* it if needed ā necessary specifically for platforms or architectures without a pre-built wheel already published for them (uncommon for pure-Python packages, more common for packages with compiled C extensions targeting a specific, less common platform), where pip falls back to building a wheel locally from the sdist rather than installing a pre-built one directly.
$ python -m build
# Produces:
# dist/my_package-1.0.0-py3-none-any.whl (wheel -- pre-built, fast to install)
# dist/my_package-1.0.0.tar.gz (sdist -- source distribution)dist/*.tar.gz (source, built on demand if needed)
2Wheels: Why File-Copying Replaced Code Execution
Before wheels became the dominant install format, installing a Python package commonly meant pip downloading a source distribution and *executing* its setup.py script directly ā a script that could, in principle, do absolutely anything, since it's arbitrary Python code running with the installing user's permissions. This was both slow (setup.py had to actually run its logic, every single install, on every single machine) and a genuine, well-documented security concern (installing a package meant trusting and running its author's arbitrary code, before you'd even had a chance to inspect what you were installing).
A wheel sidesteps both problems by shifting that work to build time, done once by the package's maintainer (or an automated build pipeline), rather than at install time, repeated identically on every single machine that installs it. unzip -l on a wheel reveals it's genuinely just a ZIP archive (with a .whl extension and a specific standardized internal structure) containing the package's files already laid out exactly where they need to go, plus a .dist-info directory holding metadata (METADATA, listing dependencies and package info; RECORD, a manifest of every file with checksums for integrity verification).
This shift ā compute once at build time, copy identically at every install time ā is a specific, deliberate instance of a broader engineering principle: work that produces the same result every time should be done once and cached/distributed, not redone redundantly on every consumer's machine. It's the same underlying idea as functools.lru_cache (compute once, reuse the result) applied at the scale of an entire packaging ecosystem rather than a single function call.
$ unzip -l my_package-1.0.0-py3-none-any.whl
my_package/__init__.py
my_package/core.py
my_package-1.0.0.dist-info/METADATA
my_package-1.0.0.dist-info/RECORD
# pip install of a wheel = extract these files to the right place. That's it.Installed identically, fast, everywhere (install time) ā no code execution
3Package Discovery: Getting the Included Files Right
A build backend needs to know exactly *which* files and directories, out of everything in your project's source tree, actually belong inside the built package ā this is package discovery configuration, declared in pyproject.toml under a build-backend-specific table ([tool.setuptools.packages.find] for setuptools, with similar equivalents for other backends). where = ["src"] (matching the src layout from the Python Project Layout lesson) tells the backend to look for packages under src/; include = ["my_package*"] filters which discovered directories actually count as part of the package being built.
Getting this configuration wrong produces failures that are often *silent* rather than loud: an overly broad discovery configuration can accidentally include tests/, development scripts, or other files that shouldn't ship to end users at all, bloating the published package and potentially leaking internal-only code; an overly narrow configuration can silently *exclude* a genuine submodule that should have been included, producing a wheel that appears to build successfully but breaks for any user who tries to import the missing piece ā a bug that a build process succeeding gives no direct signal about.
The practical defense against this silent failure mode: after building, actually inspect the resulting wheel's contents (unzip -l dist/*.whl, or a tool like check-wheel-contents) to verify it contains exactly what's expected ā no more, no less ā rather than assuming a successful build command necessarily means a correctly-composed package. This is directly analogous to the src-layout discipline from the Python Project Layout lesson: verifying the actual, real installed/built artifact, not just trusting that the build process completing without an error means everything is correct.
[tool.setuptools.packages.find]
where = ["src"]
include = ["my_package*"]
# Without correct discovery config, a build can silently:
# - Include test files that shouldn't ship to users
# - EXCLUDE a submodule that should have been includedDetermines exactly what ships ā misconfiguration often fails silently, not loudly
4Step-by-Step Breakdown
pip install used to mean 'download and RUN a setup.py script.' Modern installs mean 'download a pre-built wheel and just copy files.' That shift is why installs got so much faster and safer.
python -m build (or uv build / poetry build) reads pyproject.toml and produces TWO artifacts: a wheel and a source distribution.
A wheel is essentially a ZIP file with your package's files ALREADY laid out correctly -- installing one is just copying files, no code execution required.
Checkpoint: What does installing a wheel actually involve, at a mechanical level?
- āExtracting and copying the wheel's already-laid-out files into the right location ā no code execution required
- āRunning the package's setup code to determine what to install, same as installing from source
Package discovery configuration tells the build backend WHICH files/directories actually belong in the built package -- getting this wrong means shipping too much, or too little.
Checkpoint: What is the practical risk of incorrect package discovery configuration in pyproject.toml?
- āThe built package can silently include files that shouldn't ship (like tests) or exclude files that should (like a submodule)
- āThe build process always fails loudly with a clear error if discovery is misconfigured
Building produces a distributable artifact; Publishing Packages covers what happens once that artifact needs to reach real users via PyPI.
Parse a Real Wheel Filename. Finish extract_version_from_wheel(): a wheel's filename encodes its own metadata.
Level Up š
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported (via server-side Python execution).
Fully supported (via server-side Python execution).
Fully supported (via server-side Python execution).
Fully supported (via server-side Python execution).
Best Practices
Inspect a built wheel's actual contents rather than assuming a successful build means correct contents
Incorrect package discovery configuration often fails silently ā producing a wheel that builds successfully but is missing a real submodule or includes files that should never ship ā a direct inspection is the only reliable way to catch this.
Understand wheels as build-time-computed, install-time-copied artifacts
This explains both why modern pip installs are dramatically faster than the old setup.py-execution model, and why they're meaningfully safer ā no arbitrary code execution required at install time.
Frequent Bugs
Misconfigured package discovery silently excluding a real submodule from the built wheel, producing a package that builds and publishes successfully but breaks with ImportError for any user who tries to import that specific missing submodule.
Explicitly inspect a built wheel's file listing (unzip -l dist/*.whl) before publishing, verifying it contains exactly the expected files ā don't rely solely on a successful build command as evidence of correct package contents.
Real-World Examples
Verifying a Built Wheel Before Publishing
A team wants to add an explicit verification step to their release process, catching package-discovery misconfigurations before a broken package is published to PyPI.
$ python -m build
$ unzip -l dist/my_package-1.0.0-py3-none-any.whl | grep my_package/
my_package/__init__.py
my_package/core.py
my_package/utils.py
my_package/submodule/__init__.py # verify this expected submodule IS present
# If a submodule is missing here, package discovery config needs fixing
# BEFORE publishing, not after a user reports an ImportError