# What is core metadata?


Every Python package on PyPI carries a small text file that tells installers what it is, what it needs, and how to use it. That file is called core metadata, and its format is pinned down by a chain of PEPs stretching back to 2001.

Core metadata is the standardized, versioned wire format for describing a Python distribution. It records the name, version, summary, author, license, dependencies (`Requires-Dist`), supported interpreters (`Requires-Python`), classifiers, and project URLs. Every [PEP 517 build backend](https://pydevtools.com/handbook/explanation/what-is-pep-517.md) emits it, every installer reads it, and [PyPI](https://pydevtools.com/handbook/explanation/what-is-pypi.md) serves it separately from the package contents so resolvers can plan an install without downloading wheels.

## Where core metadata lives

The same metadata format appears in four places on the path from source tree to installed package:

- Inside a [wheel](https://pydevtools.com/handbook/reference/wheel.md): at `<dist>-<ver>.dist-info/METADATA` in the zip archive.
- Inside an [sdist](https://pydevtools.com/handbook/reference/sdist.md): as a `PKG-INFO` file at the root of the tarball.
- In a source tree during an sdist build: setuptools writes a `PKG-INFO` at the project root (or inside `*.egg-info/`) as part of `python -m build --sdist`.
- Inside `site-packages` after install: at `<dist>-<ver>.dist-info/METADATA`, queryable at runtime through `importlib.metadata.metadata("requests")`.

The content is identical across locations. The filename changes (`METADATA` for wheels and installed packages, `PKG-INFO` for sdists) for historical reasons, not technical ones.

## How the metadata version has grown

Core metadata is itself versioned. Every METADATA file opens with a `Metadata-Version:` line, and seven PEPs have bumped that version over the past twenty-plus years. Each PEP added fields that reflect how Python packaging itself evolved: from a flat list of tarballs to a resolver-driven ecosystem with SPDX licensing and lock files.

### PEP 241: the original format (Metadata 1.0)

[PEP 241](https://peps.python.org/pep-0241/) defined metadata version 1.0 in 2001. It specified the [RFC 822](https://datatracker.ietf.org/doc/html/rfc822)-style key/value format still used today and fixed the first set of fields: `Name`, `Version`, `Platform`, `Summary`, `Description`, `Keywords`, `Home-page`, `Author`, `Author-email`, `License`, and `Classifier`. The format was designed for the pre-PyPI PyPI (the Python Package Index was itself young), and the field list reflects a world where packages were installed from tarballs by hand.

### PEP 314: dependencies show up (Metadata 1.1)

[PEP 314](https://peps.python.org/pep-0314/) bumped the version to 1.1 in 2003 and added the first dependency-adjacent fields: `Requires`, `Provides`, `Obsoletes`, plus `Download-URL`. These early fields were never wired into a working resolver and are now mostly of historical interest, but PEP 314 is the point at which "this package needs that package" became something the metadata file could record.

### PEP 345: the dependency format we still use (Metadata 1.2)

[PEP 345](https://peps.python.org/pep-0345/) defined metadata version 1.2 in 2005 and is where modern dependency specification lands. It introduced `Requires-Dist`, `Provides-Dist`, `Obsoletes-Dist`, `Requires-Python`, `Requires-External`, `Project-URL`, maintainer contact fields, and, crucially, environment markers (the `; python_version >= "3.10"` syntax). Every `Requires-Dist` line a resolver reads today is a direct descendant of PEP 345.

### PEP 566: markdown READMEs and extras (Metadata 2.1)

[PEP 566](https://peps.python.org/pep-0566/) defined metadata version 2.1 in 2017 and added two fields that changed how packages present themselves. `Description-Content-Type` lets a project declare that its long description is `text/markdown` instead of reStructuredText, which is why most modern PyPI project pages render GitHub-style markdown. `Provides-Extra` formalized the "optional dependency group" convention so `pip install requests[socks]` could be understood from metadata alone rather than parsed out of `setup.py`.

### PEP 643: making sdists honest (Metadata 2.2)

[PEP 643](https://peps.python.org/pep-0643/) defined metadata version 2.2 in 2020 and added the `Dynamic` field. Before PEP 643, tools could not trust metadata in an sdist because any field might be rewritten at wheel build time (for example, a dynamic version computed from git tags). `Dynamic` flips that: an sdist's `PKG-INFO` is now authoritative for every field it does not explicitly list as `Dynamic`, so installers and indexes can read dependency information straight from an sdist without running the build backend. This is the change that makes sdist-only resolution viable.

### PEP 685: normalized extra names

[PEP 685](https://peps.python.org/pep-0685/) addressed a small but annoying corner of `Provides-Extra`: whether `Foo_Bar`, `foo-bar`, and `foo.bar` were the same extra. It settled the question by specifying that extras are normalized the same way package names are (runs of `-`, `_`, or `.` collapse to a single `-`, then lowercase), so installers can match them unambiguously.

### PEP 639: SPDX licensing (Metadata 2.4)

[PEP 639](https://peps.python.org/pep-0639/) defined metadata version 2.4 and modernized how licenses are recorded. It added `License-Expression` for [SPDX](https://spdx.dev/) identifiers (`Apache-2.0`, `MIT`, `GPL-3.0-or-later`) and `License-File` for the actual license files the wheel ships. Together they replace the free-form `License:` string with something a machine can verify, which matters for supply-chain tooling and for enterprise users who need to know exactly what license a transitive dependency is under.

## What a real METADATA file looks like

Below are the first 40 lines of the METADATA file pulled from the `requests-2.33.1` wheel on PyPI. Note the `Metadata-Version: 2.4` line at the top, the RFC-822 key/value format, the SPDX `License: Apache-2.0`, and the machine-readable `Requires-Dist` entries with environment markers for optional extras:

```text {filename="METADATA"}
Metadata-Version: 2.4
Name: requests
Version: 2.33.1
Summary: Python HTTP for Humans.
Author-email: Kenneth Reitz <me@kennethreitz.org>
Maintainer-email: Ian Stapleton Cordasco <graffatcolmingov@gmail.com>, Nate Prewitt <nate.prewitt@gmail.com>
License: Apache-2.0
Project-URL: Documentation, https://requests.readthedocs.io
Project-URL: Source, https://github.com/psf/requests
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Natural Language :: English
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: NOTICE
Requires-Dist: charset_normalizer<4,>=2
Requires-Dist: idna<4,>=2.5
Requires-Dist: urllib3<3,>=1.26
Requires-Dist: certifi>=2023.5.7
Provides-Extra: security
Provides-Extra: socks
Requires-Dist: PySocks!=1.5.7,>=1.5.6; extra == "socks"
Provides-Extra: use-chardet-on-py3
Requires-Dist: chardet<8,>=3.0.2; extra == "use-chardet-on-py3"
```

After the headers, a blank line separates the key/value block from the long project description (the README), which installers pass through to PyPI for rendering.

## Reading core metadata for an installed package

Python's standard library ships `importlib.metadata`, which parses the METADATA file of any installed distribution:

```python
from importlib.metadata import metadata

m = metadata("requests")
print(m["Name"], m["Version"])
print(m["Metadata-Version"])
print(m.get_all("Requires-Dist"))
```

`m` behaves like a `email.message.Message` because the METADATA file uses the same RFC-822 format as email headers. Use `m[key]` for single-value fields and `m.get_all(key)` for repeated fields like `Requires-Dist`, `Classifier`, and `Project-URL`.

From the command line, both pip and uv expose the same information:

```bash
python -m pip show -v requests
uv pip show requests
```

The `-v` flag on `pip show` dumps the full classifier list and entry points; `uv pip show` mirrors the same output.

## Why core metadata matters for packaging tools

Core metadata is the interface every other piece of Python packaging infrastructure depends on:

- PyPI serves it separately from the wheel. The JSON API at `https://pypi.org/pypi/<name>/<version>/json` returns parsed core metadata without the package bytes, and [PEP 658](https://peps.python.org/pep-0658/) added a matching `data-dist-info-metadata` attribute to the simple index so resolvers can fetch a single wheel's METADATA without downloading the wheel itself.
- Resolvers depend on it to pick versions. uv, pip, Poetry, and PDM all build their dependency graph by reading `Requires-Dist` and `Requires-Python` from core metadata; without it, resolution would require executing every candidate package's `setup.py`.
- Lock files embed it. [PEP 751](https://pydevtools.com/handbook/explanation/what-is-pep-751.md) `pylock.toml` files record name, version, and hashes derived from the core metadata of the exact artifacts they pin.
- Supply-chain tools verify against it. PyPI attestations and Sigstore bundles are signed over the wheel and its METADATA, so tampering with a dependency spec after the fact invalidates the signature.

## Project source metadata vs. core metadata

The [PyPA](https://www.pypa.io/) glossary distinguishes two kinds of metadata that are easy to confuse. "Project source metadata" is the `[project]` table in [pyproject.toml](https://pydevtools.com/handbook/reference/pyproject.toml.md), the human-authored format defined by [PEP 621](https://pydevtools.com/handbook/explanation/what-is-pep-621-compatibility.md). "Core metadata" is the machine-readable wire format that build backends generate from it. PEP 621 defines the mapping: a `dependencies` list in pyproject.toml becomes a sequence of `Requires-Dist` lines in METADATA, `requires-python` becomes `Requires-Python`, a `[project.optional-dependencies]` table becomes `Provides-Extra` plus extra-gated `Requires-Dist` lines, and so on.

Authors write project source metadata. Tools read core metadata. The build backend is the translator between them.

## Learn More

- [Core metadata specification](https://packaging.python.org/en/latest/specifications/core-metadata/) (packaging.python.org)
- [PyPA glossary](https://packaging.python.org/en/latest/glossary/)
- [What is a wheel?](https://pydevtools.com/handbook/reference/wheel.md)
- [What is an sdist?](https://pydevtools.com/handbook/reference/sdist.md)
- [pyproject.toml reference](https://pydevtools.com/handbook/reference/pyproject.toml.md)
- [What is PEP 621?](https://pydevtools.com/handbook/explanation/what-is-pep-621-compatibility.md)
- [What is PEP 517/518?](https://pydevtools.com/handbook/explanation/what-is-pep-517.md)
- [What is PEP 751?](https://pydevtools.com/handbook/explanation/what-is-pep-751.md)
- [What is a build backend?](https://pydevtools.com/handbook/explanation/what-is-a-build-backend.md)
