Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
250 changes: 203 additions & 47 deletions peps/pep-0825.rst
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ Default priorities

The ``default-priorities`` dictionary defines the ordering of
namespaces which is used in variant ordering. The exact algorithm is
described in the `Variant ordering`_ section.
described in the `Variant ordering and selection`_ section.

The following key is REQUIRED:

Expand Down Expand Up @@ -456,12 +456,103 @@ or resolve inconsistencies between sources; `installing wheels from
multiple sources (non-normative)`_ discusses what they can reasonably do
instead.

The reasoning behind these requirements, what they cost and what they
deliberately leave open, is summarized in `variant metadata
consistency`_ and set out in full in
:ref:`pep825-metadata-consistency`.

Variant ordering
----------------

This specification defines an ordering between different wheels based on
the presence of variant metadata.
Variant ordering and selection
------------------------------

High-level overview
'''''''''''''''''''

This specification defines an ordering of wheels based on their variant
metadata, from the most preferable to the least preferable.

The ordering of wheels by platform compatibility tags is not currently
defined by the specification, beyond a guideline that more specific
wheels should be preferred. This has not been a big problem, as usually
there is only one wheel that is compatible with the system, and where
there are more, the ordering is either clear or insignificant.

With wheel variants, there can be several compatible wheels per project
version. We define a total ordering to give package authors and users
control over wheel preference and to ensure that all tools select the
same variant without ambiguity. Tools may allow users to override this
ordering.

Every variant property is a ``namespace :: feature :: value`` triple
whose components are ranked in that order: by namespace first, then by
feature within the namespace, then by value within the feature. Only
properties compatible with the target system take part, and only the highest
ranking compatible value for each feature. The ranking for namespaces comes
from the package's variant metadata. The ranking for their features and the
ranking of values within features will be defined in a subsequent PEP.

Variant wheels sort as Python sorts lists of tuples, each wheel being
the list of its property triples ordered best first. Where one wheel's
triples run out, the longer list therefore wins. The intuition is that
this selects the wheel that makes the best use of the target hardware.

Spelled out: compare two wheels by their best-ranked property triple; if
those are equal, use the next lower-ranked property as a tiebreaker,
repeatedly until an ordering is established. Comparing all compatible
wheels this way yields the most preferred wheel.

As an example, take four variant wheels of a package that ranks the
``nvidia`` namespace above ``x86_64``:

.. code:: python

# Rankings, most preferred first. The namespace ranking comes from
# the package's variant metadata; the feature and value rankings
# will be defined in a subsequent PEP.
namespaces = ["nvidia", "x86_64"]
features = {"nvidia": ["cuda_version_lower_bound"], "x86_64": ["level"]}
values = {
"nvidia": {"cuda_version_lower_bound": ["13.0", "12.0"]},
"x86_64": {"level": ["v4", "v3", "v2"]},
}

# Each variant label maps to its properties, with only the best
# compatible value kept for every feature.
variant_wheels = {
"gpu": [("nvidia", "cuda_version_lower_bound", "13.0")],
"gpu_cpuv2": [("nvidia", "cuda_version_lower_bound", "13.0"),
("x86_64", "level", "v2")],
"gpu_cpuv4": [("nvidia", "cuda_version_lower_bound", "13.0"),
("x86_64", "level", "v4")],
"cpuv4": [("x86_64", "level", "v4")],
}

def rank(prop):
"""Rank a triple. The ranking lists put the best first, so negate
the positions to make a bigger rank mean a better property."""
namespace, feature, value = prop
return (-namespaces.index(namespace),
-features[namespace].index(feature),
-values[namespace][feature].index(value))

def sort_key(label):
"""Rank a wheel: its property ranks, best first."""
return sorted(map(rank, variant_wheels[label]), reverse=True)

sorted(variant_wheels, key=sort_key, reverse=True)
# ['gpu_cpuv4', 'gpu_cpuv2', 'gpu', 'cpuv4']

The ``gpu*`` wheels sort ahead of ``cpuv4`` because their best triple
is in the higher-ranked ``nvidia`` namespace. ``gpu_cpuv4`` beats
``gpu_cpuv2`` on their second triple, since ``v4`` outranks ``v2``. Both
beat ``gpu``, whose triples run out first, as the wheel with more
properties wins.

The ordering for non-variant wheels remains unchanged.


Ordering algorithm
''''''''''''''''''

For the purpose of ordering, the combined variant metadata for all
candidate variant wheels MUST be obtained. It can be sourced either from
Expand All @@ -476,12 +567,12 @@ features, and features into namespaces. For every namespace, the tool
MUST obtain a list of compatible features, and for every feature, a list
of compatible values. The method of obtaining these lists will be
defined in a subsequent PEP. The items in these lists will be provided
in specific order that will impact variant wheel ordering.
ordered from the most preferable to the least preferable.

The compatible wheels corresponding to a particular combination of
package name, version and build number MUST be grouped by their variant
label, and a separate group of non-variant wheels MUST be formed. The
groups of variant wheels MUST then be ordered according to the following
package name and version MUST be grouped by their variant label, and a
separate group of non-variant wheels MUST be formed. The groups of
variant wheels MUST then be ordered according to the following
algorithm:

1. Construct the ordered list of namespaces by copying the value of the
Expand All @@ -497,7 +588,7 @@ algorithm:
4. For every group, determine the most preferred value corresponding to
every variant feature present in the variant properties corresponding
to the group. This is done by finding among the values the one that
has the lowest position in the ordered property value list. After
has the lowest index in the ordered property value list. After
this step, a list of features along with their best values is
available for every variant. This is done in the
``VariantWheel.best_value_properties()`` method in the example.
Expand All @@ -509,7 +600,9 @@ algorithm:
function in the example.

6. For every group, sort the list constructed in step 4 using the sort
keys constructed in step 5, in ascending order. This is done by the
keys constructed in step 5, in ascending order. The resulting list
will be ordered from the most preferable feature to the least
preferable feature. This is done by the
``VariantWheel.sorted_properties()`` method in the example.

7. To order groups, compare their sorted lists from step 6. If the
Expand All @@ -519,17 +612,21 @@ algorithm:
found or the list in one of the groups is exhausted. In the latter
case, the group with more keys is sorted earlier. As a fallback,
if both groups have the same number of keys, they are ordered
lexically by the variant label, ascending. This is done by the
ultimate step of the example algorithm, with the comparison function
being implemented as ``VariantWheel.__lt__()``.
lexically by the variant label, ascending. The resulting list of
groups will be sorted from the most preferable to the least
preferable. This is done by the ultimate step of the example
algorithm, with the comparison function being implemented as
``VariantWheel.__lt__()``.

The algorithm sorts the group of null variant wheels last, as they
feature no variant properties. The group of non-variant wheels MUST be
placed after all the other groups.

Within every group, the wheels MUST then be ordered according to their
platform compatibility tags. After this process, the variant wheels are
sorted from the most preferred to the least preferred.
remaining properties, such as platform compatibility tags and build
numbers. This specification does not alter this ordering; it remains the
same as before. After completing this process, the wheels are sorted
from the most preferred to the least preferred.

The tools MAY provide options to override the default ordering, for
example by specifying a preference for specific namespaces, features
Expand Down Expand Up @@ -691,7 +788,7 @@ The variant markers MUST only be used in dependency specifiers and MUST
NOT take part in selecting a wheel. These markers gate the individual
dependency specifiers of a wheel that has already been selected. They
MUST be evaluated only once variant wheel selection, as described in
`variant ordering`_, has taken place.
`variant ordering and selection`_, has taken place.

Their values MUST be determined as follows:

Expand Down Expand Up @@ -878,9 +975,9 @@ behavior would be to:
7. Obtain the ordered lists of compatible variant properties. The
mechanism for this will be specified in a subsequent PEP.
8. Filter and order variants based on the lists of compatible
properties, per `variant ordering`_, and select the most preferred
variant. If no variant wheel matched, use the non-variant wheels by
their rules.
properties and select the most preferred variant, per `variant
ordering and selection`_. If no variant wheel matched, use the
non-variant wheels by their rules.
9. If multiple wheels for a given version share the same variant label,
order them by Platform compatibility tags and build number, and
select the best wheel.
Expand Down Expand Up @@ -1029,8 +1126,8 @@ When a package manager is requested to install ``torch``, in order:
the list.

5. If variant wheels with multiple different labels remain on the list,
the results are sorted per the algorithm in `variant ordering`_.
The most preferred label is selected.
the results are sorted per the algorithm in `variant ordering and
selection`_. The most preferred label is selected.

For example, in this case the ``cuda*`` wheels are ordered by their
properties, using the lists obtained in step 4. The wheels for CUDA
Expand Down Expand Up @@ -1098,9 +1195,9 @@ sources.

A tool that searches sources in priority order (for example, the "index
priority" in :pep:`766`) can order variants from one source at a time
using the `variant ordering`_ algorithm, and proceed to the next source
only if the current one has no viable candidates. No cross-source
metadata merge is necessary.
using the `variant ordering and selection`_ algorithm, and proceed to
the next source only if the current one has no viable candidates. No
cross-source metadata merge is necessary.

A tool that collates candidates from all sources before selecting among
them (for example, "version priority" in :pep:`766`) must compare wheels
Expand Down Expand Up @@ -1245,6 +1342,48 @@ due to the variant metadata being updated or being generated in a way
that does not guarantee stable bytewise output.


Variant metadata consistency
----------------------------

The `metadata consistency`_ requirements constrain two keys, and require
that their values be combinable without conflict rather than identical.
Nothing else is constrained. In particular, this PEP places no
consistency requirement on dependency metadata:
:doc:`packaging:specifications/core-metadata` permits ``Requires-Dist``
to differ between the wheels of one release when declared ``Dynamic``,
and a publisher of variant wheels may still take that route.

The requirement is what makes several of the mechanisms defined here
work at all. If the wheels of one release disagreed about namespace
order, there would be no total order over the variants, so `variant
ordering and selection`_ would have no defined output and two conforming
installers could select different wheels from the same inputs.
``pylock.toml`` inlines combined variant metadata, so non-deterministic
combination would mean that two lock runs over one release can produce
different lock files. And the `index-level metadata`_ file could neither
be generated from the uploaded wheels alone, nor be relied upon once
generated, since a resolver would then have to fetch every candidate
wheel to discover the true combined picture.

The cost is correspondingly small. The data originates from a single
source per project and is copied into each wheel at build time, so
consistency holds by construction unless the wheels of one release are
built from different inputs. Non-variant wheels and existing tools are
untouched, and since nobody publishes variant wheels yet there is no
installed base to migrate. The asymmetry runs one way: omitting the
requirement would foreclose it permanently, because once divergent
publishers exist it could not be introduced.

Divergent variant metadata is also not something anyone has asked for. A
variant label is a release-scoped identifier for a property set, so two
wheels of one release disagreeing about what a label maps to are not
expressing anything about the target platform; the identifier is simply
broken. :ref:`pep825-metadata-consistency` sets the argument out in
full, including the survey evidence on divergent dependency metadata,
the trade-offs of the alternative, and the reasoning behind the
`variant environment markers`_.


Variant environment markers
---------------------------

Expand Down Expand Up @@ -1272,7 +1411,7 @@ only ``120_real``, much as a dependency that supports a single CPU
architecture is gated on ``platform_machine``. Where a dependency does
exist for every value of a feature, it can instead be published as a
variant package and depended upon unconditionally, leaving the choice to
`variant ordering`_.
`variant ordering and selection`_.

Because of this filtering, the variant markers do not depart from the
established meaning of an environment marker. Every property in
Expand All @@ -1295,10 +1434,10 @@ Three consequences of this design are worth noting:
for every feature it declares (see `variant properties`_).
``variant_features`` and ``variant_namespaces`` therefore always list
every feature and namespace the wheel was built for. This is why the
overrides permitted in `variant ordering`_ may not reach past the
compatibility filter: a wheel selected in spite of being unsupported
could have its properties filtered away, and the dependencies gated on
them would silently disappear.
overrides permitted in `variant ordering and selection`_ may not reach
past the compatibility filter: a wheel selected in spite of being
unsupported could have its properties filtered away, and the
dependencies gated on them would silently disappear.

- For the null variant, ``variant_label`` is ``"null"`` and the three
set-valued markers are empty sets, as the null variant has zero
Expand All @@ -1311,6 +1450,21 @@ Three consequences of this design are worth noting:
resolved at build time, which is what makes the partial evaluation
described in `Backwards Compatibility`_ possible.

The same per-variant differentiation could instead be obtained by
declaring dependencies ``Dynamic`` and publishing divergent dependency
metadata for each variant wheel, and whether markers were the right
mechanism was `discussed at length and resolved in favour of markers
<https://discuss.python.org/t/pep-825-wheel-variants-package-format-split-from-pep-817/106196/186>`__.
Markers were chosen because the two routes deliver the same result while
differing in what they cost every consumer downstream: with markers the
``Requires-Dist`` lines stay textually identical across the wheels of a
release, so a resolver reading one wheel's ``METADATA`` per release
remains correct, whereas divergence would oblige every resolver to fetch
``METADATA`` per candidate wheel on every resolution. The alternative is
also considerably less available than it appears, being reachable today
only through setuptools with a ``setup.py``. The evidence and the
trade-offs are set out in :ref:`pep825-metadata-consistency`.


Backwards Compatibility
=======================
Expand Down Expand Up @@ -1520,23 +1674,6 @@ The following problems are deferred to subsequent PEPs in the series:
- building variant wheels


Open Issues
===========

These questions must be resolved before this PEP can be accepted.

Use of variant environment markers
----------------------------------

The design of the variant `environment markers`_ is not yet settled. The
same effect can be achieved through Dynamic dependencies; the open
question is whether markers are the right mechanism for obtaining it
while keeping dependency metadata static across a release. This is under
discussion in `this thread
<https://discuss.python.org/t/pep-825-wheel-variants-package-format-split-from-pep-817/106196/169>`__.
The specification reflects the current design.


Acknowledgements
================

Expand All @@ -1555,6 +1692,24 @@ and Zanie Blue.
Change History
==============

- 01-Sep-2026

- Added :ref:`pep825-metadata-consistency` as an appendix, setting out
why the `metadata consistency`_ requirements are justified and what
they cost. Summarized the metadata consistency argument in the
Rationale.
- Closed the open issue on the use of variant environment markers.
The investigation in the appendix settles the question in favour of
markers, so the Open Issues section has been removed and the
reasoning recorded in the Rationale instead.
- Removed grouping by build numbers, instead relegating them to
tie-breaking along with other wheel properties. This reverts a
potential change in behavior for non-variant wheels.
- Added a high-level overview to `variant ordering and selection`_,
clarified that the ordering of the lists used in the algorithm is
from the most preferable to the least preferable and that the
ordering by platform compatibility tags is not changed.

- 19-Aug-2026

- Strengthened the index rules to require that the irrelevant optional
Expand Down Expand Up @@ -1624,6 +1779,7 @@ Appendices
==========

- :ref:`pep825-variant-json-schema`
- :ref:`pep825-metadata-consistency`


Copyright
Expand Down
Loading
Loading