# How to Ship a LICENSE File in a Python Wheel


A `LICENSE` file in your repository does not guarantee that it reaches the {{< term "wheel" >}}. Declare the file in `pyproject.toml`, build the wheel, and inspect the archive before publishing it.

This configuration also survives a [build backend](https://pydevtools.com/handbook/explanation/what-is-a-build-backend.md) change. [Hatchling](https://pydevtools.com/handbook/reference/hatch.md) may discover a top-level `LICENSE` on its own, while [uv_build](https://pydevtools.com/handbook/reference/uv-build.md) packages it when `[project] license-files` declares it.

## Prerequisites

- [uv](https://pydevtools.com/handbook/reference/uv.md) installed
- A package project with a `[build-system]` table in `pyproject.toml`
- A UTF-8 license file in the project directory, such as `LICENSE`

## Declare the license and its file

Add both fields under the existing `[project]` table:

```toml {filename="pyproject.toml"}
[project]
license = "MIT"
license-files = ["LICENSE"]
```

`license` records the package's SPDX license expression. `license-files` tells the build backend which full license text to include in distribution archives. One field does not replace the other.

Replace `MIT` with the expression for your project. A literal filename is a valid pattern. For several legal files, use patterns such as `LICEN[CS]E*` or `NOTICE*`; patterns can also target a subdirectory. Patterns are relative to `pyproject.toml`; write paths with `/`.

## Build the wheel

Run the build from the directory containing `pyproject.toml`:

```bash
uv build --wheel
```

For a project named `licensed-demo` at version `0.1.0`, the output ends with:

```console
Successfully built dist/licensed_demo-0.1.0-py3-none-any.whl
```

`uv build` calls the backend declared in `[build-system]`, so this command works whether the project uses `uv_build`, Hatchling, [setuptools](https://pydevtools.com/handbook/reference/setuptools.md), or another [PEP 517 build backend](https://pydevtools.com/handbook/explanation/what-is-pep-517.md).

## Inspect the built wheel

List only the license entries from the newest wheel in `dist/`:

```bash
uv run --no-project python -c "from pathlib import Path; import zipfile; wheel = max(Path('dist').glob('*.whl'), key=lambda path: path.stat().st_mtime); print(*[name for name in zipfile.ZipFile(wheel).namelist() if '.dist-info/licenses/' in name and not name.endswith('/')], sep='\n')"
```

The project from the build example prints:

```console
licensed_demo-0.1.0.dist-info/licenses/LICENSE
```

The `.dist-info/licenses/` path proves that the file is inside the artifact users install. The wheel's `METADATA` file also records it in a `License-File: LICENSE` field.

## Fix a missing license entry

An empty inspection result means the newest wheel has no file under `.dist-info/licenses/`. Check that every `license-files` pattern matches at least one UTF-8 file relative to `pyproject.toml`, then rebuild the wheel.

Do not rely on a backend's automatic file discovery. Keeping `license-files` in the standard `[project]` table makes the requirement visible and preserves it when the project changes build backends.
