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 emits it, every installer reads it, and PyPI 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: at
<dist>-<ver>.dist-info/METADATAin the zip archive. - Inside an sdist: as a
PKG-INFOfile at the root of the tarball. - In a source tree during an sdist build: setuptools writes a
PKG-INFOat the project root (or inside*.egg-info/) as part ofpython -m build --sdist. - Inside
site-packagesafter install: at<dist>-<ver>.dist-info/METADATA, queryable at runtime throughimportlib.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 defined metadata version 1.0 in 2001. It specified the RFC 822-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 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 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 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 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 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 defined metadata version 2.4 and modernized how licenses are recorded. It added License-Expression for SPDX 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:
Metadata-Version: 2.4
Name: requests
Version: 2.33.1
Summary: Python HTTP for Humans.
Author-email: Kenneth Reitz <[email protected]>
Maintainer-email: Ian Stapleton Cordasco <[email protected]>, Nate Prewitt <[email protected]>
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:
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:
python -m pip show -v requests
uv pip show requestsThe -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>/jsonreturns parsed core metadata without the package bytes, and PEP 658 added a matchingdata-dist-info-metadataattribute 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-DistandRequires-Pythonfrom core metadata; without it, resolution would require executing every candidate package’ssetup.py. - Lock files embed it. PEP 751
pylock.tomlfiles 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 glossary distinguishes two kinds of metadata that are easy to confuse. “Project source metadata” is the [project] table in pyproject.toml, the human-authored format defined by PEP 621. “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.