Skip to Content
Test a Python Wheel Outside the Source Repository
Advanced
0 likes
Tutorials

Test a Python Wheel Outside the Source Repository

Catch a missing package resource by inspecting a wheel and testing its import, version, data file and console command in a clean environment.

An editable install can work while the wheel you send to someone else is incomplete. The source tree still contains files that the build never included. Test the built artifact from a clean environment outside the repository before sharing it.

This guide deliberately leaves a text resource out of a tiny package, then proves the repair. You will check the archive, installed import, console command and resource together. For package layout and metadata fundamentals, use the Python packaging course lesson.

Prepare an isolated test project

Use Python 3.10 or newer with venv support. These commands use PowerShell on Windows and explicit interpreter paths, so no activation-policy change is needed. Create a new folder named wheel-check in a writable location. Work only in this disposable folder. The package has no runtime dependencies; installing build tools initially requires network access.

mkdir wheel-check
cd wheel-check
python -m venv .build-venv
.\.build-venv\Scripts\python.exe -m pip install build
mkdir project
mkdir project/src/proof_label/labels
mkdir outside

Create these files with an editor. The empty __init__.py makes this a regular package. Save the text file as UTF-8. Do not copy a pre-existing package into the test:

wheel-check/
  project/
    pyproject.toml
    src/proof_label/
      __init__.py          (empty)
      cli.py
      labels/ready.txt     (contains: Ready for review)
  outside/

Put this deliberately incomplete configuration in project/pyproject.toml:

[build-system]
requires = ["setuptools>=77"]
build-backend = "setuptools.build_meta"

[project]
name = "proof-label"
version = "0.1.0"
requires-python = ">=3.10"

[project.scripts]
proof-label = "proof_label.cli:main"

[tool.setuptools]
package-dir = {"" = "src"}
include-package-data = false

[tool.setuptools.packages.find]
where = ["src"]
namespaces = false

Put this code in project/src/proof_label/cli.py:

from importlib.resources import files


def main() -> int:
    label = files("proof_label").joinpath("labels", "ready.txt")
    print(label.read_text(encoding="utf-8").strip())
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

include-package-data = false makes the omission intentional and independent of version-control plugins or a manifest. Python modules are still packaged; the non-Python text file needs an explicit rule.

Show why the editable check misses it

From wheel-check, install into the build environment and run from the outside folder:

.\.build-venv\Scripts\python.exe -m pip install -e ./project
cd outside
..\.build-venv\Scripts\proof-label.exe
cd ..

Expect Ready for review. This confirms the editable source works. It does not prove that the wheel contains the resource.

Build and test the broken artifact

.\.build-venv\Scripts\python.exe -m build --wheel --outdir artifacts/broken ./project
.\.build-venv\Scripts\python.exe -m zipfile -l artifacts/broken/proof_label-0.1.0-py3-none-any.whl
python -m venv .check-broken
.\.check-broken\Scripts\python.exe -m pip install --no-index --no-deps ./artifacts/broken/proof_label-0.1.0-py3-none-any.whl
cd outside
..\.check-broken\Scripts\python.exe -c "import proof_label; print(proof_label.__file__)"
..\.check-broken\Scripts\proof-label.exe
cd ..

The archive lists proof_label/cli.py and distribution metadata, but no proof_label/labels/ready.txt. The import succeeds from .check-broken's site-packages. The command then raises FileNotFoundError for the missing text file. That combination distinguishes a missing resource from a missing package or entry point. The failing command is expected in this step.

Include the data and build a new version

In pyproject.toml, change the version to 0.1.1 and add this table:

[tool.setuptools.package-data]
proof_label = ["labels/*.txt"]

An explicit package-data pattern works with include-package-data = false. Keep the broken artifact for comparison and build to a different output folder. Remove only the two generated cache folders below, inside this disposable project, to avoid stale build contents. Do not apply these deletion commands to a real repository without checking its paths.

Remove-Item -LiteralPath ./project/build -Recurse -Force -ErrorAction SilentlyContinue
Remove-Item -LiteralPath ./project/src/proof_label.egg-info -Recurse -Force -ErrorAction SilentlyContinue
.\.build-venv\Scripts\python.exe -m build --wheel --outdir artifacts/fixed ./project
.\.build-venv\Scripts\python.exe -m zipfile -l artifacts/fixed/proof_label-0.1.1-py3-none-any.whl
python -m venv .check-fixed
.\.check-fixed\Scripts\python.exe -m pip install --no-index --no-deps ./artifacts/fixed/proof_label-0.1.1-py3-none-any.whl

This archive must contain the text resource. The fresh environment has no editable install, so it cannot silently fall back to the source tree.

Check the installed artifact as one test

Save the following as outside/smoke.py. It calls the console command by the current environment's scripts path, which avoids accidentally testing a command from another Python installation.

from importlib import metadata
from importlib.resources import files
from pathlib import Path
import subprocess
import sys
import sysconfig

import proof_label

package_file = Path(proof_label.__file__).resolve()
assert Path(sys.prefix).resolve() in package_file.parents, package_file
assert metadata.version("proof-label") == "0.1.1"
assert files("proof_label").joinpath("labels", "ready.txt").read_text(
    encoding="utf-8"
).strip() == "Ready for review"

scripts = Path(sysconfig.get_path("scripts"))
command = scripts / ("proof-label.exe" if sys.platform == "win32" else "proof-label")
result = subprocess.run([str(command)], check=True, capture_output=True, text=True)
assert result.stdout.strip() == "Ready for review", result.stdout
print("PASS: installed import, version, data file, and console command")
cd outside
..\.check-fixed\Scripts\python.exe -I smoke.py
cd ..

Expect PASS: installed import, version, data file, and console command. The -I option isolates Python from user-site and PYTHONPATH influences. The test also checks that the import lives inside the new environment. Keep the archive listing and this test result together when checking a release.

When an installed check fails

  • Import fails: inspect package discovery and the wheel listing before debugging resources.
  • Command is missing: inspect [project.scripts] and reinstall a newly built wheel in a fresh environment.
  • Resource is missing: compare the package-data pattern with the exact path and filename case inside the archive.
  • Version stays old: confirm the interpreter and exact artifact path; avoid a wildcard that could select an earlier wheel.
  • Your real package has dependencies: this example's --no-deps is intentional. Test those dependencies separately with a prepared wheelhouse or your normal installer.

Try a resource regression

Rename ready.txt to ready.csv and update the code to read that filename, but leave the package-data rule unchanged. Would the import and version checks catch the missing resource?

Answer: no. They still pass because the Python package and metadata are present. The resource assertion and command test fail. Add the required CSV pattern, build another version from clean generated directories, then repeat the complete installed check.

References: setuptools data-file configuration and the Python Packaging User Guide.

About

Catch a missing package resource by inspecting a wheel and testing its import, version, data file and console command in a clean environment.

Responsible
Jay P
Last Update
09/11/2026
Reading Time
6 min
Was this helpful?
​ 0 ​ 0

Comments (0)

Sign in to comment

No comments yet.