PEP 826 – Wheel Variants: Providers
- Author:
- Jonathan Dekhtiar <jonathan at dekhtiar.com>, Michał Górny <mgorny at quansight.com>, Konstantin Schütze <konstin at mailbox.org>, Ralf Gommers <ralf.gommers at gmail.com>, Andrey Talman <atalman at meta.com>, Charlie Marsh <charlie at astral.sh>, Michael Sarahan <msarahan at gmail.com>, Eli Uriegas <eliuriegas at meta.com>, Barry Warsaw <barry at python.org>, Donald Stufft <donald at stufft.io>, Andy R. Terrel <andy.terrel at gmail.com>
- Discussions-To:
- Discourse thread
- Status:
- Draft
- Type:
- Standards Track
- Topic:
- Packaging
- Created:
- 17-Feb-2026
- Post-History:
- 17-Feb-2026
Abstract
This PEP is a followup to PEP 825 that defines how variant properties are governed and how their compatibility is determined. This is primarily done via opt-in plugins that are Python packages specified in variant metadata, but can also be vendored or reimplemented by the tools. Package maintainers and users can only supply static compatibility data to avoid the need for plugins.
Motivation
PEP 825 introduced a protocol for recording additional compatibility data in binary packages, in the form of variant properties. It provided the foundations by organizing the variant properties into namespaces. However, it did not specify how variant namespaces are governed, nor how to determine which variant properties are compatible with a particular use system. This PEP aims to fill this gap.
The PEP is specifically aiming to make variant wheels suitable for satisfying three use cases:
- Variants that express platform compatibility, for example GPU or CPU capabilities. In this case, the goal is to select the best wheel that is compatible with the particular system.
- Variants that express non-platform properties, such as different BLAS/LAPACK or OpenMP implementations, or debug builds. Here all variants that were built are compatible, and the goal is to provide user with the ability to explicitly select a non-default variant.
- Variants that express compatibility with different dependency versions, particularly aiming to express Application Binary Interface (ABI) compatibility. The goal is to enable matching variants against other packages, especially if they adapt new versions of common dependencies at different rates.
Specification
Definitions
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.
Providers
Variant properties, as defined in PEP 825, are organized into variant namespaces. Every variant namespace used in a variant wheel MUST be governed by a variant provider. Variant providers supply ordered lists of compatible features and feature values corresponding to their namespaces, as required by PEP 825.
The namespace abi_dependency is reserved for the ABI Dependency
Variant Provider. All
other namespaces used MUST be defined in the provider information
dictionary in variant metadata. For every namespace, the following
rules apply, in order:
- A variant provider MAY be enabled or disabled by default. The tools MUST provide a way to explicitly enable a provider, and MAY provide a way to disable one. If a provider is disabled, the tools MUST assume that the list of compatible features is empty and they MUST NOT use the provider in any way. Otherwise, proceed to step 2.
- A variant provider metadata MAY include a static list of compatible features and their values. If that is the case, the tools MUST use the static lists. Otherwise, proceed to step 3.
- The tools SHOULD implement a way for the user to provide a static list of compatible features and their values. If the user provides said list, the tools MUST use it. Otherwise, proceed to step 4.
- If no static list of compatible features and their values is provided, the variant provider metadata MUST specify a list of required Python packages that provide a variant provider plugin. The tools MAY choose to vendor or reimplement plugin packages at their leisure. If that is the case, they MUST obtain the list from their implementation. Otherwise, proceed to step 5.
- The tools MAY provide list of trusted packages that are permitted to be used default. They SHOULD provide a way for the user to trust additional packages. If the list of required provider packages contains any untrusted package, the tools MUST assume that the list of compatible features is empty and they MUST NOT install or use the provider packages. Otherwise, proceed to step 6.
- The tools MUST install the specified provider packages and query them via the provider plugin API. The tools SHOULD use an isolated virtual environment for that purpose.
Variant metadata
This PEP extends the metadata introduced in PEP 825 with additional
providers key. Therefore, the metadata has the following structure:
(root)
|
+- $schema
+- default-priorities
+- variants
+- providers
+- {namespace}
+- optional : bool = False
+- plugin-api : str | None = None
+- requires : list[str] = []
+- static-properties
+- {feature} : list[str] = []
This structure corresponds to the version 0.0.2 of the format. An
update of the proposed JSON schema for the current format version is
included in the Appendix of this PEP. The schema is available in
Appendix: JSON Schema for Variant Metadata.
Provider information
providers is a dictionary, the keys are namespaces, the values are
dictionaries with provider information. It specifies how to install and
use variant providers.
The use of provider information is described in the Providers and Provider plugin API sections.
One of the following keys MUST be present in the provider information dictionary:
static-properties: dict[str, list[str]]: A dictionary whose keys specify set of compatible features and values the ordered lists of their respective compatible values. Since the dictionaries in JSON are unsorted, if more than one key is specified, then the order for all features MUST be specified indefault-priorities.feature.{namespace}.requires: list[str]: A list of zero or more package dependency specifiers, that are used to install the provider plugin. If the dependency specifiers include environment markers, these are evaluated against the environment where the plugin is being installed and the requirements for which the markers evaluate to false are filtered out. In that case, at least one dependency MUST remain present in every possible environment. Additionally, ifplugin-apiis not specified, the first dependency present after filtering MUST always evaluate to the same API endpoint.
If both are provided, the requires key MUST be ignored.
A provider information dictionary MAY additionally contain the following keys:
optional: bool: Whether the provider is optional. Defaults tofalse. If it istrue, the provider is disabled by default and needs to be enabled explicitly.plugin-api: str: The API endpoint for the plugin. If it is specified, it MUST be an object reference as explained in the API endpoint section. If it is missing, the package name from the first dependency specifier inrequiresis used, after replacing all-characters with_in the normalized package name.
Provider plugin API
High level design
All variants published on a single index for a specific package version MUST use the same provider for a given namespace. Attempting to load more than one plugin for the same namespace in the same release version MUST result in a fatal error. While multiple plugins for the same namespace MAY exist across different packages, release versions or indexes (such as when a plugin is forked due to being unmaintained), they are mutually exclusive within any single release version on an index.
To make it easier to discover and install plugins, they SHOULD be published in the same indexes that the packages using them. In particular, packages published to PyPI MUST NOT rely on plugins that need to be installed from other indexes.
Except for namespaces reserved as part of this PEP, installable Python packages MUST be provided for plugins. The entire dependency tree of such a plugin MUST be installable using non-variant wheels, and variant wheels MUST NOT be used.
As noted in the Providers section, these plugins can also be reimplemented by tools needing them. In the latter case, the resulting reimplementation does not need to follow the API defined in this section.
A plugin implemented as Python package exposes callables that are called via:
{API endpoint}.{callable name}({arguments}...)
These can be implemented either as module-level functions, class methods or static methods. The specifics are provided in the subsequent sections.
API endpoint
The location of the plugin code is called an “API endpoint”, and it is expressed using the object reference notation following the Entry points specification:
{import_path}(:{object_path})?
An API endpoint specification is equivalent to the following Python pseudocode:
import {import_path}
if "{object_path}":
plugin = {import_path}.{object_path}
else:
plugin = {import_path}
API endpoints are used in two contexts:
- in the
plugin-apikey of variant metadata, either explicitly or inferred from the package name in therequireskey. This is the primary method of using the plugin when building and installing wheels. - as the value of an installed entry point in the
variant_pluginsgroup. The name of said entry point is insignificant. This is OPTIONAL but RECOMMENDED, as it permits variant-related utilities to discover variant plugins installed to the user’s environment.
Variant feature config class
The variant feature config class is used as a return value in plugin API functions. It defines a single variant feature, along with a list of possible values. Depending on the context, the order of values MAY be significant. It is defined using the following protocol:
from abc import abstractmethod
from typing import Protocol
class VariantFeatureConfigType(Protocol):
@property
@abstractmethod
def name(self) -> str:
"""Feature name"""
raise NotImplementedError
@property
@abstractmethod
def multi_value(self) -> bool:
"""Does this property allow multiple values per variant?"""
raise NotImplementedError
@property
@abstractmethod
def values(self) -> list[str]:
"""List of values, possibly ordered from most preferred to least"""
raise NotImplementedError
The instance MUST provide the following properties or attributes:
name: strspecifying the feature name.multi_value: boolspecifying whether the feature is allowed to have multiple corresponding values within a single variant wheel. If it isFalse, then it is an error to specify multiple values for the feature.values: list[str]specifying feature values. In contexts where the order is significant, the values MUST be ordered from the most preferred to the least preferred.
Plugin interface
The plugin interface MUST follow the following protocol:
from abc import abstractmethod
from typing import Protocol
class PluginType(Protocol):
@classmethod
@abstractmethod
def get_supported_configs(cls) -> list[VariantFeatureConfigType]:
"""Get ordered lists of compatible features and their values"""
raise NotImplementedError
The plugin interface MUST provide the following function:
get_supported_configs() -> list[VariantFeatureConfigType]that returns a list of feature names and their values that are compatible with the system the plugin is running on. The variant feature and value lists MUST be ordered from the most preferred to the least preferred.
Example implementation
from dataclasses import dataclass
@dataclass
class VariantFeatureConfig:
name: str
values: list[str]
multi_value: bool
# internal -- provided for illustrative purpose
_ALL_GPUS = ["narf", "poit", "zort"]
def _get_current_version() -> int:
"""Returns currently installed runtime version"""
... # implementation not provided
def _is_gpu_available(codename: str) -> bool:
"""Is specified GPU installed?"""
... # implementation not provided
class MyPlugin:
@staticmethod
def get_supported_configs() -> list[VariantFeatureConfig]:
current_version = _get_current_version()
if current_version is None:
# no runtime found, system not supported at all
return []
return [
VariantFeatureConfig(
name="min_version",
# [current, current - 1, ..., 1]
values=[str(x) for x in range(current_version, 0, -1)],
multi_value=False,
),
VariantFeatureConfig(
name="gpu",
# this may be empty if no GPUs are supported --
# 'example :: gpu feature' is not supported then;
# but wheels with no GPU-specific code and only
# 'example :: min_version' could still be installed
values=[x for x in _ALL_GPUS if _is_gpu_available(x)],
multi_value=True,
),
]
Future extensions
The future versions of this specification, as well as third-party extensions MAY introduce additional attributes on the plugin instances. The implementations SHOULD ignore them.
For best compatibility, all private attributes SHOULD be prefixed with
an underscore (_) character to avoid incidental conflicts with
future extensions.
ABI Dependency Variant Provider (Optional)
This section describes an OPTIONAL extension to the wheel variant specification. Tools that choose to implement this feature MUST follow this specification. Tools that do not implement this feature MUST treat the variants using it as incompatible, and SHOULD inform users when such wheels are skipped.
The variant namespace abi_dependency is reserved for expressing that
different builds of the same version of a package are compatible with
different versions or version ranges of a dependency. This namespace
MUST NOT be listed in the provider information dictionary, and can
only appear in a built wheel variant property.
Within this namespace, zero or more properties can be used to express
compatible dependency versions. For each property, the feature name MUST
be the normalized name of the
dependency, whereas the value MUST be a valid release segment of
a public version identifier, as defined by the
Version specifiers specification.
It MUST contain up to three version components, that are matched against
the installed version same as the =={value}.* specifier. Notably,
trailing zeroes match versions with fewer components (e.g. 2.0
matches release 2 but not 2.1). This also implies that the
property values have different semantics than PEP 440 versions, in
particular 2, 2.0 and 2.0.0 represent different ranges.
Versions with nonzero epoch are not supported.
| Variant Property | Matching Rule |
|---|---|
abi_dependency :: torch :: 2 |
torch==2.* |
abi_dependency :: torch :: 2.9 |
torch==2.9.* |
abi_dependency :: torch :: 2.8.0 |
torch==2.8.0.* |
Multiple variant properties with the same feature name can be used to indicate wheels compatible with multiple providing package versions, e.g.:
abi_dependency :: torch :: 2.8.0
abi_dependency :: torch :: 2.9.0
This means the wheel is compatible with both PyTorch 2.8.0 and 2.9.0.
Rationale
The primary use case for providers is determining platform compatibility, which implies that they need to be used at install time. The specification proposes a plugin mechanism using Python packages, with the interface inspired by PEP 517. Such a mechanism has a few advantages:
- The individual plugins can be governed independently, by the
stakeholders having necessary knowledge and hardware. Additional
compatibility axes (new CPUs, GPUs) do not impose direct maintenance
costs on tools interacting with variant wheels, nor on
centrally-maintained libraries such as
packaging. - The plugins can be updated as frequently as necessary, without being tied to tool release schedules.
- The plugins provide a unified interface for testing new providers. New plugins can be developed and tested locally without having to patch multiple tools, and released to the public after proving the concept.
At the same time, it is understood that installing additional Python packages and running the code from them at install time introduces additional attack vector (as discussed in security implications). For this reason, plugin packages are entirely opt-in, and a few mechanisms are provided to improve the user experience without compromising security:
- Users can provide static compatibility lists to avoid querying the providers. This also permits deploying packages for different systems than the one running the installer.
- Tools can maintain their own lists of trusted provider plugins that are enabled by default, or they can vendor or reimplement some providers. This is entirely voluntary, as not to impose maintenance effort on tool maintainers. At the same time, the ability to reimplement providers avoids introducing a performance bottleneck on tools that aren’t written in Python.
- Variant wheels can include static lists of compatible properties, to facilitate variants that do not need querying platform capabilities, such as builds done against different BLAS/LAPACK libraries.
Furthermore, individual providers can be disabled by default (made optional), to introduce variants that can only be selected explicitly, for example debug or experimental builds of packages.
Installing provider plugins in isolated environments is recommended, as that permits tools to automatically deploy them without affecting the system packages. However, this is not a requirement to permit other options. For example, build backends may prefer reusing the isolated build environment for this.
The requires and plugin-api keys follow the precedent of
build-system.requires and build-system.build-backend keys of
PEP 517. However, following the criticism of that design,
plugin-api has been made optional and defaults to being inferred
from the package name.
The ABI Dependency Variant Provider is defined separately, as it needs to interact with the dependency resolver. To avoid adding significant complexity to the plugin API and at the same time restricting the actual implementation, it has been made a special case. It is entirely optional to avoid adding maintenance burden to tool maintainers.
Backwards Compatibility
This PEP does not introduce any new backwards compatibility considerations, compared to PEP 825.
Security Implications
This PEP introduces a plugin system for querying the platform capabilities. Tools may install these packages and execute the code within them during dependency resolution or wheel processing. This elevates the supply-chain attack potential by introducing two new points for malicious actors to inject arbitrary code payload:
- Publishing a version of a variant provider plugin or one of its dependencies with malicious code.
- Introducing a malicious variant provider plugin in an existing package metadata.
Admittedly, such attacks can already be done to the package’s dependencies. However, in some cases the affected tools are executed with elevated privileges (such as when installing packages for multi-user systems), while the package itself will only be run with regular user privileges. Therefore, variant provider plugins could introduce a Remote Code Execution vulnerability with elevated privileges.
A similar issue already exists in the packaging ecosystem when packages are installed from source distributions, with build backends and other build dependencies are being installed and executed. However, various tools operating purely on wheels, as well as users using tool-specific options to disable the use of source distributions, have been relying on the assumption that no such code execution will happen. To uphold this assumption, the proposal makes plugin packages opt-in.
Unfortunately, an opt-in system creates a risk of security fatigue. Users wishing to use variant wheels may start blanket-enabling all use of provider plugins, reintroducing the RCE danger. To avoid this and improve user experience, the PEP permits tool maintainers to provide opt-out experience for selected plugins. A subsequent PEP will give further recommendations for improved user experience.
How to Teach This
This PEP is focused on installing variant wheels. The primary source of
information for the users will be the user interface of installers,
supplemented by their documentation and installation instructions of
specific packages publishing variant wheels. The documentation present
on packaging.python.org will need to be updated as well.
Ideally, in the most common use cases variants will work out of the box and non-expert users will not need to be aware of them, much like they do not need to be aware of Platform compatibility tags.
Expert users will need guidance that will largely be specific to particular installer implementation, as it involves user interface decisions. The following topics may need to be covered, depending on the features implemented by the installer:
- how to enable installing untrusted variant provider packages, and what are the security implications of that
- how to enable optional providers
- how to generate and provide static compatibility data, enabling deployment for remote targets
- how to explicitly select a specific variant
- how to alter variant selection, for example by specifying preferred properties or filtering out undesirable properties
The topic of teaching package maintainers will be addressed in a subsequent PEP, along with building variant wheels.
Reference Implementation
The variantlib project contains a reference implementation of this PEP.
A client for installing variant wheels is implemented in a uv branch.
Rejected Ideas
An approach without provider plugins
Rather than introducing provider plugins, the rules governing every variant namespace could be defined via PEPs. However, such an approach would be less scalable and impose additional effort on stakeholders, PEP editors and tool maintainers.
Every new namespace would have to go through standardization process, followed by explicit implementation process. Deployment of new variant properties would be entirely dependent on tool updates. The added maintenance cost could lead to support for less popular variant axes not being accepted, or lack of feature parity between different tools.
Open Issues
This PEP is concerned with installing wheels only. A subsequent PEP will address building variant wheels.
Acknowledgements
This work would not have been possible without the contributions and feedback of many people in the Python packaging community. In particular, we would like to credit the following individuals for their help in shaping this PEP (in alphabetical order):
Alban Desmaison, Bradley Dice, Chris Gottbrath, Dmitry Rogozhkin, Emma Smith, Geoffrey Thomas, Henry Schreiner, Jeff Daily, Jeremy Tanner, Jithun Nair, Keith Kraus, Leo Fang, Mike McCarty, Nikita Shulga, Paul Ganssle, Philip Hyunsu Cho, Robert Maynard, Vyas Ramasubramani, and Zanie Blue.
Change History
- 17-Feb-2026
- Initial version, split from PEP 817 draft.
- Namespaces have been removed from the provider plugin API. Instead, the namespace is named by the package in variant metadata.
- The
enable-ifkey has been removed from provider information, as it was deemed redundant. - The
static-propertiestable has been moved into provider information.
Appendices
Copyright
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.
Source: https://github.com/python/peps/blob/main/peps/pep-0826.rst
Last modified: 2026-02-23 13:56:17 GMT