PyPI · Format PEP 817 · 825 · 777 PyPI Onboarding · No. 06 6,709 words · ~34 min Onboarding

Wheels & What's (Wheel)Next

A wheel filename has five fields and has not changed since 2013. Here is what those fields can say, what they cannot, and the public proposals trying to add a sixth.

Orientation

A filename is an API

Ask the Simple API for a project and you get back a list of links. In the oldest form of that API, defined by PEP 503, a link carries almost nothing except a filename and a hash. The installer has to decide, from the filename alone, whether a given file can run on the machine in front of it. So the filename is not a label. It is the protocol.

The Binary Distribution Format specification gives the shape in one line:

{distribution}-{version}(-{build tag})?-{python tag}-{abi tag}-{platform tag}.whl

That template comes from PEP 427, The Wheel Binary Package Format 1.0, written by Daniel Holth, created 20 September 2012 and accepted on 16 February 2013. The template has not changed since. Newer PEPs have added things inside the archive, and the packaging.python.org history section names several of them: PEP 639 for a licenses directory, PEP 770 for SBOM files, PEP 815 for deprecating the old signature files. None of them touched the five fields on the outside.

Thirteen years is a long run for a routing key. It held because the fields it does have are the right ones for the machine most people were installing onto: an interpreter, an ABI, an operating system, a CPU architecture. It is under strain now because a growing share of Python installs care about hardware the filename has no word for.

This guide is a map of proposals, not a changelog. Everything in the second half is public, in flight, and unaccepted. Warehouse implements none of it today.

Where the neighbouring guides sit

Upload mechanics, size enforcement in warehouse/forklift/legacy.py, and PEP 658 sidecar metadata extraction belong to No. 05, Uploads, Then and Now. How the Simple API grew its JSON form and its per-file attributes belongs to No. 04, PyPI's API History. This guide stays on the format and the filename.

The format

The three tags, decoded

The first two fields are bookkeeping. {distribution} is the normalized project name and {version} is a PEP 440 version. The optional {build tag} breaks ties between two builds of the same release; the spec says to sort it "as a two-item tuple with the first item being the initial digits as an int." Note that requirement, because it is the hinge the whole variant proposal turns on later: a build tag starts with a digit.

The last three fields do the real work. They come from PEP 425 and now live at Platform Compatibility Tags.

{python tag}
The implementation and version. The spec: "The Python tag indicates the implementation and version required by a distribution." Implementations get two-letter codes, py for generic Python, cp for CPython, pp for PyPy, ip for IronPython, jy for Jython. So py3 means any Python 3, and cp314 means CPython 3.14.
{abi tag}
"The ABI tag indicates which Python ABI is required by any included extension modules." none for pure Python, cp314 for a build tied to one CPython ABI, abi3 for the stable ABI, which is the one tag in this field that buys forward compatibility across interpreter releases.
{platform tag}
The operating system and architecture, derived from sysconfig.get_platform() with hyphens and periods turned into underscores. any, win_amd64, macosx_11_0_arm64. Linux is the complicated one.

Linux is complicated because "Linux" is not an ABI. PEP 600, by Nathaniel J. Smith and Thomas Kluyver, created 3 May 2019 and now Final, replaced the old one-PEP-per-tag treadmill with a perennial scheme: manylinux_${GLIBCMAJOR}_${GLIBCMINOR}_${ARCH}. A wheel tagged manylinux_2_17_x86_64 promises to work on any mainstream distribution running glibc 2.17 or newer on x86-64. The three named tags became aliases: manylinux1 is manylinux_2_5, manylinux2010 is manylinux_2_12, manylinux2014 is manylinux_2_17. PEP 656, by Tzu-ping Chung, created 17 March 2021 and also Final, did the same job for musl-based distributions with musllinux_${MUSLMAJOR}_${MUSLMINOR}_${ARCH}.

numpy-2.3.2-cp314-cp314-manylinux_2_17_x86_64.whl distribution numpy PEP 503 PEP 427 version 2.3.2 PEP 440 build tag absent PEP 427 starts with a digit python tag cp314 PEP 425 abi tag cp314 PEP 425 platform tag manylinux_2_17_x86_64 PEP 425, PEP 600 glibc 2.17 or newer variant label proposed PEP 825 draft, not implemented Five hyphen-separated fields today. Six when a build tag is present. PEP 825 would spend the sixth slot differently.
Fig. 1. Every field of a wheel filename, with the specification that governs it. The three striped fields are the compatibility tags: they are the only part an installer consults when deciding whether a file will run. The two dashed boxes are not in this filename. The right-hand one is not in any filename yet.

Two more mechanics matter. Tags compress: a wheel that works on several tag combinations can join them with periods, so py2.py3-none-any is one file claiming two tag triples (the Cartesian product of two Python tags by one ABI tag by one platform tag: py2-none-any and py3-none-any). And the whole system is closed. There is no escape hatch, no free-text field, no vendor extension. If a fact about the target machine does not fit in one of those three tags, the filename cannot carry it.

The limits

What the axes cannot say

PEP 817 states the problem in its own words:

“These tags are limited to expressing the most fundamental properties of the Python interpreter, operating system and the broad CPU architectures. They cannot express anything more detailed, including non-CPU hardware requirements or library ABI constraints.”

PEP 817 → The limitations of platform compatibility tags

Which GPU. Which CUDA or ROCm version. Which CPU instruction set beyond the baseline. Which BLAS library the extension modules were linked against. All of that is invisible to the filename, so projects that depend on it have built five different workarounds, and PEP 817 catalogues them all.

Separate indexes

PyTorch publishes to its own index and encodes the accelerator in the local version segment, +cu130, +rocm6.4, +cpu. Installing means picking the right URL first:

pip install torch --index-url https://download.pytorch.org/whl/cu129

PEP 817 puts a number on the friction: "on 2025-12-05, 552 out of 8136 (6.8%), of issues on uv's issue tracker contained the term ‘torch’." It also notes the security cost, that this pattern has led to supply chain attacks, and that the response was to mirror every dependency.

Separate package names

XGBoost ships xgboost for the GPU build and xgboost-cpu for the other one. The PEP's objection is not aesthetic. A downstream maintainer cannot say "either of these will do," the two packages install overlapping files, and nothing in Python packaging can express that two packages are mutually exclusive, so an installer will happily put both in one environment and let the second overwrite the first.

Then there is the squatting surface. PEP 817 counts it: CuPy "has already registered a total of 55 cupy* packages with different names, most of them never actually used," purely so nobody else takes them. The PEP's worked example of the risk is a malicious numpy-cuda.

Extras, source-distribution trampolines, and fat wheels

JAX uses extras, twelve of them, most mutually exclusive and none able to say so, with the result that a plain pip install jax produces a non-functional install. FlashAttention publishes no wheels at all, only a source distribution that detects the platform, downloads a wheel from elsewhere, and hands it to the installer, which the PEP notes "requires running arbitrary code at install time" and puts the download outside the package manager's control entirely.

The last option is to put everything in one file. PEP 817 on that:

“Including all possible variants in a single wheel is another option, but this leads to excessively large artifacts, wasting bandwidth and leading to slower installation times for users who only need one specific variant. In some cases, such artifacts cannot be hosted on PyPI because they exceed its size limits.”

PEP 817 → Bundled universal packages

Those limits are real numbers in real code. warehouse/constants.py sets MAX_FILESIZE = 100 * ONE_MIB and MAX_PROJECT_SIZE = 10 * ONE_GIB, and per-project overrides are capped at UPLOAD_LIMIT_CAP = ONE_GIB. That cap is not just application logic; warehouse/packaging/models.py carries a database check constraint, projects_upload_limit_max_value, defined as upload_limit <= 1073741824. The upload guide covers how the check runs while the request is still streaming.

Above the code sits policy, and it lives in a README. pypi/support is where a limit increase is requested, and its README says what gets granted:

“Small (up to 200MiB) upload limits are generally granted for the following reasons: project contains small pre-trained models; project contains a small JAR file / executable; any other sufficiently motivated reason.”

“Large (more than 200MiB) upload limits are generally granted for the following reasons: project contains large compiled binaries to maintain platform/architecture/GPU support; project is associated with an established project or organization.”

pypi/support → README.md

And what does not:

“Upload limits are generally denied for the following reasons: project makes nightly releases and/or is published frequently; project contains large pre-trained machine learning models; project includes a large JAR file / executable; project includes a runtime for another programming language; project is not associated with an established project or organization; project has not made a best-effort to reduce release size.”

pypi/support → README.md

Read those two lists next to the fat-wheel workaround and the shape of the problem is clear. "Large compiled binaries to maintain platform/architecture/GPU support" is on the granted list because there is no other way to ship them. A format that let a project publish one wheel per target would take pressure off a policy that currently absorbs it by hand, one support ticket at a time.

The cost of the safe default

The alternative to a fat wheel is a thin one that gives up performance. NumPy builds for a baseline CPU and dispatches at runtime, which, PEP 817 notes, "usually doesn't let the code benefit from compiler optimizations outside the few select functions." The PEP quotes an archspec paper for the size of the gap: compiling GROMACS for Cascade Lake's AVX-512 gives "an additional 18% performance improvement relative to using AVX2 instructions, with a speedup of about 70% compared to a generic GROMACS installation with only SSE2."

The same argument applies to linked libraries rather than instruction sets. PEP 817 has a section on it:

“Packages such as NumPy and SciPy can be built using different BLAS / LAPACK libraries. Users may wish to choose a specific library for improved performance on a particular hardware, or based on license considerations. Furthermore, different libraries may use different OpenMP implementations, whereas using a consistent implementation across the stack can avoid degrading performance through spawning too many threads.”

PEP 817 → BLAS / LAPACK variants

That last clause is the interesting one. It is not only about picking the fastest library. It is about every package in one process agreeing on which library they picked, which is a fact about a whole environment that no single filename can currently state.

Checkpoint 1 · the format and its edges

Q1A wheel is named foo-1.0-2-cp314-cp314-win_amd64.whl. What is the 2?

C. The build tag sits between version and python tag, and the Binary Distribution Format spec sorts it as a tuple whose first item is "the initial digits as an int." That leading digit is why a six-field filename is unambiguous today, and why PEP 825 has to add a rule about digits before it can spend the sixth slot on something else. D is the trap: PEP 825 is a draft, and its label would go at the end, not here.

Q2A project wants to ship one wheel per GPU generation. Which field of the filename holds that?

A. PEP 600 defines the platform tag as an operating system, a glibc floor, and a CPU architecture, and nothing else. The abi tag covers the Python ABI, not linked native libraries. B is the closest to plausible, since the build tag's tail is free text, but it is a tie-breaker between builds of one release, not a compatibility axis: an installer given two build tags picks the higher one, it does not check whether either fits the machine.

The initiative

WheelNext

The work is organized in the open at wheelnext.dev. The site describes itself in one sentence:

“WheelNext is an open-source initiative aiming to improve the user experience in the Python packaging ecosystem, specifically around the scientific computing and machine/deep learning space.”

wheelnext.dev

The site credits NVIDIA with leading the initial kickoff, and credits pypackaging-native as a source of ideas and a template. It lists nine proposals, and wheel variants is one of them:

Two of those names should look familiar. "How To Reinvent the Wheel" is PEP 777, in section six below. "Upload 2.0 API for Python Package Indexes" is PEP 694, which has its own guide at No. 05. WheelNext is not a parallel standards body; it is a place where several proposals that all end up in the PEP process get worked on together, and it keeps its own writeups alongside summits, a glossary, and slide decks.

Naming individuals beyond that is not this guide's job. The PEPs carry their own author lists, and those are the names with signatures on the proposals.

The proposal

PEP 817, then PEP 825

PEP 817, Wheel Variants: Beyond Platform Tags, was created 10 December 2025 with eleven authors: Jonathan Dekhtiar, Michał Górny, Konstantin Schütze, Ralf Gommers, Andrey Talman, Charlie Marsh, Michael Sarahan, Eli Uriegas, Barry Warsaw, Donald Stufft and Andy R. Terrel. It was posted to Discourse on 24 January 2026. Its status is Draft.

It proposed the whole system at once: the filename change, the metadata, the index file, plus a plugin interface where "variant providers" run on the installing machine to detect what hardware is present. That last part drew the sharpest questions in the thread. Damian Shaw, on the expectation that installers would vendor providers: "I don't agree that this PEP should be setting this expectation on all Python package installers. For example, has a pip maintainer volunteered to do this for pip?" Paul Moore, on the extra metadata fetches during resolution: "I'd really like to see some performance figures before we agree on the proposed design, as it'll be very hard to change the design later if it turns out to impose unacceptable overheads."

On 17 February 2026 the same eleven authors published PEP 825, Wheel Variants: Package Format, with Paul Moore as PEP-Delegate. Its status is also Draft. PEP 825 does not use the word "supersedes." It says:

“This PEP is part of a larger variant wheel design that was originally proposed as PEP 817. However, due to its complexity, we decided to split it into smaller parts that build one upon another. This PEP is the first in the series, providing foundations including the file format along with necessary metadata, index support and basic tool algorithms.”

PEP 825 → Abstract

The split moved the contentious half out. PEP 825 explicitly defers "determining which variant properties are compatible with the system" to a later PEP, which is exactly the plugin question. Paul Moore, in the PEP 825 thread: "This PEP is a huge improvement over the previous one... it looks like a solid PEP at this point." The thread has run to more than a hundred and sixty posts, and the PEP's own Open Issues section points at post 169 for an unsettled question about variant environment markers.

The mechanism, in three pieces

One. A label in the filename. PEP 825 changes the PEP 427 template to:

{distribution}-{version}(-{build tag})?-{python tag}-{abi tag}-{platform tag}(-{variant label})?.whl

The label is human-readable, per the PEP's own abstract, but that's a courtesy to people, not a promise to installers. It carries no meaning an installer is allowed to parse; the label is a key, and the properties mapping is the only source of truth for what it means. (The PEP considered and rejected hash-derived labels precisely because a hash would make the label opaque and hard for a human to reason about.) The normative text: "The variant label MUST be non-empty and consist only of 0-9, a-z, _ and . ASCII characters (^[0-9a-z_.]+$)." One label reserved: "The label null is reserved and always corresponds to the variant with zero properties, called a null variant. This variant acts as a fallback variant that is always compatible." The three worked examples from the PEP:

numpy-2.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl            # non-variant
numpy-2.3.2-cp313-cp313t-musllinux_1_2_x86_64-x86_64_v3.whl  # variant
numpy-2.3.2-cp313-cp313t-musllinux_1_2_x86_64-null.whl       # null variant

The disambiguation rule is one line: "The Python tag component MUST NOT start with a digit." A six-field filename whose fourth-from-last field starts with a digit has a build tag. One whose does not has a variant label. With packaging 26.3 installed, a tool that does full filename verification rejects the new shape outright:

>>> parse_wheel_filename("numpy-2.3.2-cp313-cp313t-musllinux_1_2_x86_64-x86_64_v3.whl")
InvalidWheelFilename: Invalid build number: cp313 in
'numpy-2.3.2-cp313-cp313t-musllinux_1_2_x86_64-x86_64_v3'

For a tool built on strict parsing like this one, that failure is the design working: an installer that has never heard of variants sees an invalid filename, skips the file, and takes a non-variant wheel instead. PEP 825 is explicit that this isn't universal, though. It only promises fail-closed behaviour for tools that fully verify the filename grammar, and it names current, commonly used installers as examples of tools that do. A tool with looser parsing may not verify that deeply, and the PEP warns such a tool could consume some or all variant wheels as if they were ordinary wheels, which is a correctness bug waiting to happen, not something the format prevents.

Two. A properties mapping. The meaning of a label lives in *.dist-info/variant.json inside the wheel. A property is a three-part tuple of namespace, feature, and value, written {namespace} :: {feature_name} :: {feature_value}. Real examples from the PEPs: x86_64 :: level :: v3, nvidia :: cuda_version_lower_bound :: 12.8, blas_lapack :: library :: openblas. Namespaces are governed independently, so the group that defines what nvidia features mean is not the group that defines x86_64.

Three. A file on the index, so nobody downloads a wheel to find out. This is the part with PyPI's name on it:

“When a package version that includes at least one variant wheel is hosted on an index, a corresponding {name}-{version}-variants.json file MUST be hosted as well. The purpose of the file is to optimize variant metadata lookups by removing the need to fetch multiple variant wheels during dependency resolution.”

PEP 825 → Index-level metadata

The file has the same structure as the in-wheel metadata, but index-scoped: it "MUST list all variants available on the package index for the package version in question." It is not immutable and "MAY be updated in a backward compatible way at any point," for instance when a new variant is added to an existing release. And the index gets a choice: "Variant indexes MAY elect to either auto-generate the file from the uploaded variant wheels or allow the user to manually generate it themselves and upload it to the index."

Ordering is the fourth piece, and it is metadata rather than code, though the current PEP defines less of it than an earlier draft did. What exists today is a default-priorities.namespace list, which ranks namespaces against each other:

[variant.default-priorities]
namespace = ["x86_64", "aarch64", "blas_lapack"]

An earlier draft of this PEP also defined default-priorities.feature and default-priorities.property, ranking features within a namespace and values within a feature the same way. The PEP's own change history says both were removed, and that ordering features and values, along with how any of this reaches a project's pyproject.toml, is left to a subsequent PEP. So today, the project can only say which namespace it cares about most; what happens inside a namespace, and how a project publishes its preference at all, isn't decided yet.

NEW IN PEP 825 · steps 4 to 8 Everything else is what installers already do. Step 5 is the request that lands on the index. Warehouse would have to serve that file. Step 7 needs the compatible property list, which PEP 825 defers to a later PEP. 1–2. query the index, pick a version version constraints only 3. filter by platform compatibility tags PEP 425 / 600 / 656 · today's world ends here 4. any wheel carrying a variant label? no → proceed as with non-variant wheels no 5. GET {name}-{version}-variants.json one request, not one download per wheel 6–7. labels → properties → ordered list default-priorities, then what the machine supports 8. pick the best variant none match → non-variant wheels, or the null variant 9–10. tie-break, then read dependencies build tag decides ties; markers see the chosen label
Fig. 2. The install path from PEP 825's "Installing a package from an index." Green steps are unchanged. Orange steps are new. The PEP's own summary: "steps 4 through 8 are introduced specifically for variant wheels. The remaining steps correspond to the current installer behavior."

One design decision in the thread is worth keeping. PEP 817 capped variant labels at sixteen characters. PEP 825's normative text drops the cap and requires only a non-empty match on ^[0-9a-z_.]+$, after h-vetinari pointed out on Discourse that the PEP's own examples were bumping into it: "If the PEP itself already runs into this limitation (without a contrived example)... it sounds like 16 is too short?" The counter-argument in the thread was Windows path length. Small change, and a fair sample of how the review is going: mostly technical, mostly specific, and mostly resolved in the text.

The other proposal

PEP 777, and the question underneath

PEP 777, How to Re-invent the Wheel, by Emma Harper Smith, sponsored by Barry Warsaw, with Paul Moore as PEP-Delegate, was created 9 October 2024 and posted to Discourse the next day. Its status is Draft. Its Resolution field is empty.

It is older than the variant PEPs and asks a smaller, more awkward question: how does the wheel format ever change at all? PEP 427 already says an installer MUST abort on a wheel whose major version it does not support. So the first project to publish a wheel 2.0 breaks every user on an older installer. The PEP is blunt about the consequence:

“An increase of the wheel major version has yet to happen, partially because such a change has the potential to be catastrophically disruptive.”

PEP 777 → Rationale

The proposal has two halves. The first makes the version visible before download. Today the wheel version lives in .dist-info/WHEEL, inside the archive, so a resolver cannot see it without fetching the file. PEP 777 adds a Wheel-Version field to core metadata, which puts it in the PEP 658 metadata sidecar that indexes already serve. Then it makes resolvers use it: "Resolvers, in the process of selecting a wheel to install, MUST check a candidate wheel's Wheel-Version, and ignore incompatible wheel files." Skipping beats aborting. If a wheel is from the future, the resolver moves on rather than failing the install.

The second half is the part people remember. Even with that rule in place, the installers already deployed do not follow it, and the PEP estimates the wait:

“It could take upwards of four years before the majority of users are on updated resolvers... To allow for experimentation and faster adoption of 2.0 wheels, this PEP proposes a change to the file extension of the wheel file format, from .whl to .whlx for all future wheel versions.”

PEP 777 → First Major Version Bump Must Change File Extension

A new extension means old installers never consider the file at all, so wheel 2.0 could be published to PyPI immediately without hurting anyone. The Discourse thread spent time on the spelling. Paul Moore: "If we're going to require a new wheel file extension, and it's not going to be 3 characters anyway, why not just use .wheel?" Eric V. Smith: "Please let's pick an extension that's not pronounced ‘wheel’, which is how everyone I've talked with pronounces ‘whl’." The extension in the PEP text today is still .whlx.

PEP 777 also fences in what a future wheel may do. Wheels "MUST remain ZIP format files as the outer container format," the .dist-info directory must stay at the archive root uncompressed, installed wheels must stay readable by importlib.metadata on every supported CPython, and no revision may use a compression format missing from the standard library. It does not specify wheel 2.0. It writes the rules that wheel 2.0 would have to follow, and notes that a future revision "may add or remove filename components, such as the build tag or the platform tag."

Two proposals, two different doors

PEP 825 adds a field to the existing wheel 1.0 filename and relies on old tools rejecting it as malformed. PEP 777 defines how a future wheel could change the format outright, extension included. They are not competing, and PEP 825 does not need PEP 777 to land. Read PEP 777 as the escape hatch for changes too large to smuggle past an old parser.

The close

What would land on Warehouse's desk

Everything below is conditional. None of it is built, and neither PEP is accepted.

Start from what Warehouse does today, because it is stricter than most people expect. warehouse/forklift/legacy.py calls packaging.utils.parse_wheel_filename(filename) on every .whl upload and rejects anything it cannot parse. Then it checks each parsed tag against _valid_platform_tag(), which tests a hand-maintained set: a static _allowed_platforms list holding any, the Windows tags, the legacy manylinux1/2010/2014 spellings and two linux_armv* entries, plus regular expressions for macOS, iOS, Android, PEP 600 and PEP 656 Linux, and Emscripten, each with its own architecture allowlist. An unrecognised tag gets a 400 and a metric:

f"Binary wheel '{filename}' has an unsupported platform tag '{tag.platform}'."

The comment above that list explains the strictness, and it is worth reading before proposing an addition: "defining new platform ABI compatibility tags that don't have a python.org binary release to anchor them is a complex task that needs more than just OS+architecture info."

So a variant wheel uploaded to PyPI today fails at the first step. Not on policy, on parsing. Warehouse pins packaging==26.2, and parse_wheel_filename reads the sixth field as a build number and raises InvalidWheelFilename. If PEP 825 is accepted, the work looks roughly like this.

  1. 01

    Accept the filename

    warehouse/forklift/legacy.py · upstream pypa/packaging first

    The parse happens in pypa/packaging, not in Warehouse. The library learns the new template and the "python tag MUST NOT start with a digit" rule, Warehouse takes the new pin, and only then can the upload path tell a variant label from a malformed name.

  2. 02

    Validate what arrives

    PEP 825 → Implementation requirements

    The PEP names indexes directly: "Tools that are in a position to reject invalid data at the point it enters the ecosystem, such as a package index accepting an upload, SHOULD do so." That means the label regex, the reserved null label, the presence of *.dist-info/variant.json, and the rule that one label maps to one property set across a whole version. The last one is a cross-file check, which nothing in the current upload path does.

  3. 03

    Produce and serve the variants file

    Simple API · {name}-{version}-variants.json

    PEP 825 lets an index either accept the file from the uploader or build it. If Warehouse builds it, the input is the variant.json of every variant wheel in the release, combined, and the PEP notes the result "does not depend on the order in which the wheels are processed." The file is mutable, which is unlike every other artifact PyPI serves, and it has to appear in "all the responses where the variant wheels are included."

  4. 04

    Decide when it is complete

    PEP 825 → Publishing variant wheels on an index

    A release uploaded one file at a time is a release whose variants file is wrong until the last file lands. The PEP's own suggestion for the fix names the neighbouring guide: the index "should use some mechanism to defer publishing it until the release is fully uploaded (for example, PEP 694)." See No. 05 for what that mechanism is and why it does not exist yet.

The serving-side questions, how a new file type gets its attributes in the Simple API, which API version declares it, and what a JSON response has to carry, belong to No. 04 in this series. What matters here is that PEP 825 does not ask Warehouse to understand GPUs. It asks Warehouse to accept a longer filename, check a regex, and publish one derived JSON file per release.

Checkpoint 2 · the proposals

Q3An installer that predates PEP 825 and fully verifies wheel filenames meets numpy-2.3.2-cp313-cp313t-musllinux_1_2_x86_64-x86_64_v3.whl. What happens?

A. A six-field filename means a build tag to an old parser, and a build tag must start with a digit. packaging 26.3 raises InvalidWheelFilename: Invalid build number: cp313. Failing to parse means skipping the file, not failing the install, which is why PEP 825 can add a field to wheel 1.0 without a flag day, for tools built this way. D describes PEP 777's world, where the abort is the problem PEP 777 exists to route around. The PEP itself is careful to scope this to tools with full filename verification; a looser parser might not reject the file at all, and could install a variant wheel it has no business installing.

Q4Under PEP 825, where does an installer learn what the label x86_64_v3 actually requires?

B. The label may read as human-friendly, but an installer isn't allowed to parse it; the mapping is data, not the label itself. The index file exists precisely so a resolver does not have to do D, though the PEP allows falling back to reading the metadata out of candidate wheels when no index file is served, and notes that only one wheel per distinct label needs inspecting. A confuses the two sides: providers detect what the machine supports, and PEP 825 deliberately defers that half to a later PEP.

Q5Why does PEP 777 propose .whlx rather than keeping .whl?

D. The PEP's estimate is "upwards of four years" before most users run a resolver that filters on Wheel-Version. A different extension makes old tools ignore the files today. C is wrong in the opposite direction: PEP 777 requires that wheels "MUST remain ZIP format files as the outer container format." B belongs to PEP 825, which is a separate proposal.

Where everything is

ThingWhereWhy you'd look
The filename template Binary Distribution Format · origin PEP 427 The five fields, the build-tag sort rule, and what lives inside the archive
The tag vocabulary Platform Compatibility Tags · PEP 600, PEP 656 Every legal python, abi and platform tag, and the manylinux/musllinux schemes
What PyPI accepts today warehouse/forklift/legacy.py → _allowed_platforms, _valid_platform_tag() The hand-maintained allowlist. Adding a platform tag to the ecosystem means editing this
Size limits, in code warehouse/constants.py · constraint in warehouse/packaging/models.py 100 MiB per file, 10 GiB per project, overrides capped at 1 GiB in the database itself
Size limits, in policy pypi/support → README.md The granted and denied lists a volunteer quotes back at a request
The variant proposal PEP 825 (format) · PEP 817 (full design, problem statement) 817 for the motivation and the catalogue of workarounds; 825 for the normative format
Format evolution PEP 777 Wheel-Version, resolver skipping, .whlx, and the limits on any future wheel
The initiative wheelnext.dev Nine proposals, summits, and the writeups that precede the PEPs
Upload mechanics No. 05, Uploads, Then and Now How the size checks run mid-stream, and what PEP 694 would change about release atomicity
Status, checked 14 August 2026

PEP 817 (Wheel Variants: Beyond Platform Tags), created 10 December 2025: Draft. PEP 825 (Wheel Variants: Package Format), created 17 February 2026, PEP-Delegate Paul Moore: Draft, with an open issue on variant environment markers still unresolved in its own text. PEP 777 (How to Re-invent the Wheel), created 9 October 2024, PEP-Delegate Paul Moore: Draft, Resolution empty. For comparison, the specifications this guide treats as settled are Final: PEP 427, PEP 425, PEP 600, PEP 656.

Warehouse implements none of the three. A search for "variant" across the codebase returns nothing relevant, the platform-tag allowlist has no variant concept, and the pinned packaging==26.2 cannot parse a variant filename. Verify all of this against the live PEP index rather than trusting this box; draft status is the one fact in this guide with a short shelf life.

If you're going to work on this

  1. Read PEP 817's Motivation before PEP 825's Specification. 825 is the normative document, but it deliberately outsources the problem statement, saying only that "the problem space has been explored in greater detail in PEP 817." Reading 825 first gives you a mechanism with no reason attached.
  2. Check the Status header every time. Three of the documents here are drafts by eleven or one authors under active review, and threads with a hundred and sixty posts move text. Quote the PEP, not a summary of it, and re-fetch before you quote.
  3. When you want to know what PyPI accepts, read _valid_platform_tag(), not the spec. The specification describes a tag space; Warehouse enforces a smaller, curated subset, and the gap between them is where upload rejections come from.
  4. File tag-parsing and installer-selection bugs against pypa/packaging or pypa/pip, not pypi/warehouse. Warehouse calls the parser; it does not own it. Different org, different maintainers, as No. 00 explains.

Pyramid terms, plain English

ACL / __acl__
A list of (allow or deny, who, permission) rules a resource can define. Pyramid's authorization layer checks it before letting a request through.
config.scan()
Walks the codebase looking for decorators like @view_config and registers whatever it finds. Without a scan, a decorator alone does nothing.
directive
A custom method bolted onto Pyramid's Configurator object (via add_directive), so a plugin can offer its own verb, like config.add_periodic_task(...), instead of every caller repeating the same boilerplate.
find_service()
Asks the registry for "whatever implements this interface" and gets an object back, without the caller knowing or caring which concrete class it is.
includeme(config)
The one function every Pyramid plugin must define. config.include("some.package") calls it and hands over the Configurator, so the plugin can register its own routes, views, and services.
predicate
A condition attached to a route or view, like request_method="POST", that Pyramid checks before it will match that route or view to an incoming request.
register_service_factory()
The other half of find_service(): tells the registry "when someone asks for this interface, call this factory to build the answer."
registry
Pyramid's central lookup table, assembled once at startup, holding every route, view, service, and setting the running app knows about.
resource
In traversal, an object reachable by walking a chain of __getitem__ calls from the root. The traversal equivalent of a URL path segment.
root factory
The function that builds the very first resource in a traversal chain, given a request. Every other resource is reached by indexing into it.
subscriber
A function registered to run automatically whenever a specific event fires, like a transaction commit, without the code that fires the event knowing the subscriber exists.
traversal
Pyramid's alternative to matching a URL against a pattern: walk a resource tree one path segment at a time via __getitem__, and whatever object you land on becomes the view's context.
tween
Short for "between." Middleware that sits between Pyramid's router and the view, in an explicit, orderable chain, wrapping the whole request/response cycle.
venusian
The library config.scan() actually uses. A decorator like @view_config just leaves a note for venusian to find later; it does nothing by itself until scanned.
view deriver
A wrapper Pyramid can apply to every view during registration, used for cross-cutting behavior (like injecting a metrics call) without every view author remembering to add it by hand.