From b867962bbdd1b7dc7f0fbd3ad9df198cef0295c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Thu, 19 Feb 2026 20:06:34 +0100 Subject: [PATCH 01/14] pep-0826: Start writing the motivation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-0826.rst | 135 ++++++++++++++++++ .../pep-0826/appendix-variant-json-schema.rst | 11 ++ peps/pep-0826/variant_schema-0.0.2.json | 113 +++++++++++++++ 3 files changed, 259 insertions(+) create mode 100644 peps/pep-0826.rst create mode 100644 peps/pep-0826/appendix-variant-json-schema.rst create mode 100644 peps/pep-0826/variant_schema-0.0.2.json diff --git a/peps/pep-0826.rst b/peps/pep-0826.rst new file mode 100644 index 00000000000..156769641e8 --- /dev/null +++ b/peps/pep-0826.rst @@ -0,0 +1,135 @@ +PEP: 826 +Title: Wheel Variants: Providers +Author: Jonathan Dekhtiar , + Michał Górny , + Konstantin Schütze , + Ralf Gommers , + Andrey Talman , + Charlie Marsh , + Michael Sarahan , + Eli Uriegas , + Barry Warsaw , + Donald Stufft , + Andy R. Terrel +Discussions-To: https://discuss.python.org/t/pep-817-split-wheel-variants-package-format/106196 +Status: Draft +Type: Standards Track +Topic: Packaging +Created: 17-Feb-2026 +Post-History: `17-Feb-2026 `__ + +Abstract +======== + + + +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: + +1. 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. + +2. 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. + +3. 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`. + + +Rationale +========= + + +Backwards Compatibility +======================= + +Security Implications +===================== + + +How to Teach This +================= + + +Reference Implementation +======================== + +The `variantlib `__ project +contains a reference implementation of a complete variant wheel +solution. It is compliant with this PEP, but also goes beyond it, +providing example solutions to `open issues`_. + +A client for installing variant wheels is implemented in a +`uv branch `__. + + +Rejected Ideas +============== + + + +Open Issues +=========== + + +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. + + +Appendices +========== + +- :ref:`pep826-variant-json-schema` + + +Copyright +========= + +This document is placed in the public domain or under the +CC0-1.0-Universal license, whichever is more permissive. diff --git a/peps/pep-0826/appendix-variant-json-schema.rst b/peps/pep-0826/appendix-variant-json-schema.rst new file mode 100644 index 00000000000..3e4d24cb0c5 --- /dev/null +++ b/peps/pep-0826/appendix-variant-json-schema.rst @@ -0,0 +1,11 @@ +:orphan: + +.. _pep826-variant-json-schema: + +Appendix: JSON Schema for Variant Metadata +========================================== + +.. literalinclude:: variant_schema-0.0.2.json + :language: json + :linenos: + :name: variant-json-schema diff --git a/peps/pep-0826/variant_schema-0.0.2.json b/peps/pep-0826/variant_schema-0.0.2.json new file mode 100644 index 00000000000..31f45928036 --- /dev/null +++ b/peps/pep-0826/variant_schema-0.0.2.json @@ -0,0 +1,113 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://wheelnext.dev/schema/variant-0.0.1.json", + "title": "Variant metadata, v0.0.1", + "description": "The format for variant metadata (variant.json) and index-level metadata ({name}-{version}-variants.json)", + "type": "object", + "properties": { + "default-priorities": { + "description": "Default priorities for ordering variants", + "type": "object", + "properties": { + "namespace": { + "description": "Namespaces (in order of preference)", + "type": "array", + "items": { + "type": "string", + "pattern": "^[a-z0-9_]+$" + }, + "minItems": 1, + "uniqueItems": true + }, + "feature": { + "description": "Default feature priorities (by namespace)", + "type": "object", + "patternProperties": { + "^[a-z0-9_]+$": { + "description": "The most preferred features", + "type": "array", + "items": { + "type": "string", + "pattern": "^[a-z0-9_]+$" + }, + "minItems": 0, + "uniqueItems": true + } + }, + "additionalProperties": false, + "uniqueItems": true + }, + "property": { + "description": "Default property priorities (by namespace)", + "type": "object", + "patternProperties": { + "^[a-z0-9_]+$": { + "description": "Default property priorities (by feature name)", + "type": "object", + "patternProperties": { + "^[a-z0-9_]+$": { + "description": "The most preferred feature values", + "type": "array", + "items": { + "type": "string", + "pattern": "^[a-z0-9_.]+$" + }, + "minItems": 0, + "uniqueItems": true + } + }, + "additionalProperties": false, + "uniqueItems": true + } + }, + "additionalProperties": false, + "uniqueItems": true + } + }, + "additionalProperties": false, + "uniqueItems": true, + "required": [ + "namespace" + ] + }, + "variants": { + "description": "Mapping of variant labels to properties", + "type": "object", + "patternProperties": { + "^[a-z0-9_.]{1,16}$": { + "type": "object", + "description": "Mapping of namespaces in a variant", + "patternProperties": { + "^[a-z0-9_.]+$": { + "description": "Mapping of feature names in a namespace", + "patternProperties": { + "^[a-z0-9_.]+$": { + "description": "List of values for this variant feature", + "type": "array", + "items": { + "type": "string", + "pattern": "^[a-z0-9_.]+$" + }, + "minItems": 1, + "uniqueItems": true + } + }, + "uniqueItems": true, + "additionalProperties": false + } + }, + "uniqueItems": true, + "additionalProperties": false + } + }, + "additionalProperties": false, + "uniqueItems": true + } + }, + "required": [ + "default-priorities", + "variants" + ], + "additionalProperties": false, + "uniqueItems": true +} From 5fa0503df75838e70b29378202980c9ed1289fb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Fri, 20 Feb 2026 17:45:40 +0100 Subject: [PATCH 02/14] First specification draft (absolutely minimal) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-0826.rst | 387 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 387 insertions(+) diff --git a/peps/pep-0826.rst b/peps/pep-0826.rst index 156769641e8..8787d67cdcc 100644 --- a/peps/pep-0826.rst +++ b/peps/pep-0826.rst @@ -63,6 +63,393 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", 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 <#abi-dependency-variant-provider-optional>`_. All +other namespaces used MUST be defined in the `provider information`_ +dictionary in `variant metadata`_. For every namespace, the following +rules apply, in order: + +1. 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 supported features is empty and they MUST NOT use + the provider in any way. Otherwise, proceed to step 2. + +2. A variant provider metadata MAY include a static list of supported + features and their values. If that is the case, the tools MUST use + the static lists. Otherwise, proceed to step 3. + +3. The tools SHOULD implement a way for the user to provide a static + list of supported features and their values. If the user provides + said list, the tools MUST use it. Otherwise, proceed to step 4. + +4. If no static list of supported 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. + +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 supported features is empty and they MUST NOT install or use the + provider packages. Otherwise, proceed to step 6. + +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: + +.. code:: text + + (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 +:ref:`pep826-variant-json-schema`. + + +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 supported features and values the ordered lists of + their respective supported values. Since the dictionaries in JSON are + unsorted, if more than one key is specified, then the order for all + features MUST be specified in + ``default-priorities.feature.{namespace}``. + +- ``requires: list[str]``: A list of zero or more package + :ref:`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, if ``plugin-api`` is 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 + to ``false``. If it is ``true``, 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 in ``requires`` is 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. However, 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: + + .. code:: text + + {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 +:doc:`packaging:specifications/entry-points`: + +.. code:: text + + {import_path}(:{object_path})? + +An API endpoint specification is equivalent to the following Python +pseudocode: + +.. code:: python + + import {import_path} + + if "{object_path}": + plugin = {import_path}.{object_path} + else: + plugin = {import_path} + +API endpoints are used in two contexts: + +a. in the ``plugin-api`` key of variant metadata, either explicitly or + inferred from the package name in the ``requires`` key. This is the + primary method of using the plugin when building and installing + wheels. + +b. as the value of an installed entry point in the ``variant_plugins`` + group. 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: + +.. code:: python + + 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: str`` specifying the feature name. + +- ``multi_value: bool`` specifying whether the feature is allowed to + have multiple corresponding values within a single variant wheel. If + it is ``False``, 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: + +.. code:: python + + from abc import abstractmethod + from typing import Protocol + + + class PluginType(Protocol): + @classmethod + @abstractmethod + def get_supported_configs(cls) -> list[VariantFeatureConfigType]: + """Get supported configs for the current system""" + 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 +'''''''''''''''''''''' + +.. code:: python + + 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 :ref:`normalized name ` of the +dependency, whereas the value MUST be a valid release segment of +a public version identifier, as defined by the +:doc:`packaging:specifications/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.: + +.. code:: text + + 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 ========= From 4aa85480858f3f8f912806cf9c60c3ce69ba59ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Sat, 21 Feb 2026 09:25:29 +0100 Subject: [PATCH 03/14] Start writing the rationale MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-0826.rst | 80 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 67 insertions(+), 13 deletions(-) diff --git a/peps/pep-0826.rst b/peps/pep-0826.rst index 8787d67cdcc..daea9c202d8 100644 --- a/peps/pep-0826.rst +++ b/peps/pep-0826.rst @@ -81,29 +81,29 @@ rules apply, in order: 1. 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 supported features is empty and they MUST NOT use + that the list of compatible features is empty and they MUST NOT use the provider in any way. Otherwise, proceed to step 2. -2. A variant provider metadata MAY include a static list of supported +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. 3. The tools SHOULD implement a way for the user to provide a static - list of supported features and their values. If the user provides + list of compatible features and their values. If the user provides said list, the tools MUST use it. Otherwise, proceed to step 4. -4. If no static list of supported 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. +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. 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 supported features is empty and they MUST NOT install or use the + of compatible features is empty and they MUST NOT install or use the provider packages. Otherwise, proceed to step 6. 6. The tools MUST install the specified provider packages and query them @@ -152,8 +152,8 @@ 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 supported features and values the ordered lists of - their respective supported values. Since the dictionaries in JSON are + 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 in ``default-priorities.feature.{namespace}``. @@ -320,7 +320,7 @@ The plugin interface MUST follow the following protocol: @classmethod @abstractmethod def get_supported_configs(cls) -> list[VariantFeatureConfigType]: - """Get supported configs for the current system""" + """Get ordered lists of compatible features and their values""" raise NotImplementedError The plugin interface MUST provide the following function: @@ -453,10 +453,64 @@ 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 considerations`_). +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. + +The `ABI Dependency Variant Provider +<#abi-dependency-variant-provider-optional>`_ 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 ======================= + Security Implications ===================== From a8127e60d9a370ad193e3978c4a65ec5e1cea87e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Sat, 21 Feb 2026 09:26:12 +0100 Subject: [PATCH 04/14] Fix ref MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-0826.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/peps/pep-0826.rst b/peps/pep-0826.rst index daea9c202d8..2538e6e783b 100644 --- a/peps/pep-0826.rst +++ b/peps/pep-0826.rst @@ -474,7 +474,7 @@ advantages: 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 considerations`_). +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: From b3ccd305d3b7112663dd76c02642f145b535e788 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Sat, 21 Feb 2026 14:58:54 +0100 Subject: [PATCH 05/14] Fix API endpoint formatting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-0826.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/peps/pep-0826.rst b/peps/pep-0826.rst index 2538e6e783b..6f5d7d252d0 100644 --- a/peps/pep-0826.rst +++ b/peps/pep-0826.rst @@ -213,9 +213,9 @@ not need to follow the API defined in this section. A plugin implemented as Python package exposes callables that are called via: - .. code:: text +.. code:: text - {API endpoint}.{callable name}({arguments}...) + {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 From a9d7bdb4dd87322f95d6fd0a79f998c1c3ea8082 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Sat, 21 Feb 2026 15:14:39 +0100 Subject: [PATCH 06/14] Finish the rationale MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-0826.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/peps/pep-0826.rst b/peps/pep-0826.rst index 6f5d7d252d0..a4e91baf25c 100644 --- a/peps/pep-0826.rst +++ b/peps/pep-0826.rst @@ -498,6 +498,18 @@ 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 <#abi-dependency-variant-provider-optional>`_ is defined separately, as it needs to interact with the dependency resolver. To avoid adding From cc10d68f2ca4f7f58eeb82f5e7803d6402a86817 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Sat, 21 Feb 2026 15:23:27 +0100 Subject: [PATCH 07/14] Provider packages must be installable via non-variant wheels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-0826.rst | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/peps/pep-0826.rst b/peps/pep-0826.rst index a4e91baf25c..a9eb5c81131 100644 --- a/peps/pep-0826.rst +++ b/peps/pep-0826.rst @@ -205,10 +205,14 @@ 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. However, 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. +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: From 6e7b824e1b501ac2ad4abdf4aa2b12c24e4e0e00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Sat, 21 Feb 2026 15:29:04 +0100 Subject: [PATCH 08/14] List spec changes from PEP 817 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-0826.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/peps/pep-0826.rst b/peps/pep-0826.rst index a9eb5c81131..139ab632627 100644 --- a/peps/pep-0826.rst +++ b/peps/pep-0826.rst @@ -577,6 +577,13 @@ 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-if`` key has been removed from `provider information`_, + as it was deemed redundant. + - The ``static-properties`` table has been moved into `provider + information`_. Appendices From 23c7560b25c1c1ceeb1144214a4b0ff9c93e0c18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Sat, 21 Feb 2026 15:57:24 +0100 Subject: [PATCH 09/14] Start filling other bits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-0826.rst | 53 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/peps/pep-0826.rst b/peps/pep-0826.rst index 139ab632627..a5dba85cf29 100644 --- a/peps/pep-0826.rst +++ b/peps/pep-0826.rst @@ -526,10 +526,47 @@ 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: + +1. Publishing a version of a variant provider plugin or one of its + dependencies with malicious code. +2. Introducing a malicious variant provider plugin in an existing + package metadata. + +While such attacks are already possible at the package dependency level, +it needs to be emphasized that in some scenarios the affected tools are +executed with elevated privileges, for example when installing packages +for multi-user systems. On the other hand, the code in installed +packages will be used with regular user privileges afterwards. +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, where build backends and other +build dependencies are 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 ================= @@ -550,11 +587,27 @@ A client for installing variant wheels is implemented in a 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 ================ From 76e46b8f9ae9d3cef544367039186cf90f949220 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Mon, 23 Feb 2026 14:14:49 +0100 Subject: [PATCH 10/14] Add an abstract and reword security for readability MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-0826.rst | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/peps/pep-0826.rst b/peps/pep-0826.rst index a5dba85cf29..3d9a31470ce 100644 --- a/peps/pep-0826.rst +++ b/peps/pep-0826.rst @@ -21,6 +21,12 @@ Post-History: `17-Feb-2026 Date: Mon, 23 Feb 2026 14:26:15 +0100 Subject: [PATCH 11/14] Update JSON schema MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-0826/variant_schema-0.0.2.json | 67 ++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 2 deletions(-) diff --git a/peps/pep-0826/variant_schema-0.0.2.json b/peps/pep-0826/variant_schema-0.0.2.json index 31f45928036..c245219ce36 100644 --- a/peps/pep-0826/variant_schema-0.0.2.json +++ b/peps/pep-0826/variant_schema-0.0.2.json @@ -1,10 +1,14 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://wheelnext.dev/schema/variant-0.0.1.json", - "title": "Variant metadata, v0.0.1", + "$id": "https://wheelnext.dev/schema/variant-0.0.2.json", + "title": "Variant metadata, v0.0.2", "description": "The format for variant metadata (variant.json) and index-level metadata ({name}-{version}-variants.json)", "type": "object", "properties": { + "$schema": { + "description": "JSON schema URL", + "type": "string" + }, "default-priorities": { "description": "Default priorities for ordering variants", "type": "object", @@ -70,6 +74,63 @@ "namespace" ] }, + "providers": { + "description": "Mapping of namespaces to provider information", + "type": "object", + "patternProperties": { + "^[A-Za-z0-9_]+$": { + "type": "object", + "description": "Provider information", + "properties": { + "plugin-api": { + "description": "Object reference to plugin class", + "type": "string", + "pattern": "^([a-zA-Z0-9._]+ *: *[a-zA-Z0-9._]+)|([a-zA-Z0-9._]+)$" + }, + "optional": { + "description": "Whether the provider is optional (disabled by default)", + "type": "boolean" + }, + "requires": { + "description": "Dependency specifiers for how to install the plugin", + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "minItems": 0, + "uniqueItems": true + }, + "static-properties": { + "description": "Static properties (by feature name)", + "type": "object", + "patternProperties": { + "^[a-z0-9_]+$": { + "description": "Ordered value list", + "type": "array", + "items": { + "type": "string", + "pattern": "^[a-z0-9_.]+$" + }, + "minItems": 0, + "uniqueItems": true + }, + "additionalProperties": false, + "uniqueItems": true + } + } + }, + "additionalProperties": false, + "uniqueItems": true, + "anyOf": [ + {"required": ["requires"]}, + {"required": ["static-properties"]} + ] + } + }, + "additionalProperties": false, + "uniqueItems": true + }, "variants": { "description": "Mapping of variant labels to properties", "type": "object", @@ -105,7 +166,9 @@ } }, "required": [ + "$schema", "default-priorities", + "providers", "variants" ], "additionalProperties": false, From b0d43c5b5301a56d420beaa30d1d3b64959fc90b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Mon, 23 Feb 2026 14:27:56 +0100 Subject: [PATCH 12/14] Align filename with PEP 825 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-0826/appendix-variant-json-schema.rst | 2 +- .../{variant_schema-0.0.2.json => variant-schema-0.0.2.json} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename peps/pep-0826/{variant_schema-0.0.2.json => variant-schema-0.0.2.json} (100%) diff --git a/peps/pep-0826/appendix-variant-json-schema.rst b/peps/pep-0826/appendix-variant-json-schema.rst index 3e4d24cb0c5..4ec33c8a97d 100644 --- a/peps/pep-0826/appendix-variant-json-schema.rst +++ b/peps/pep-0826/appendix-variant-json-schema.rst @@ -5,7 +5,7 @@ Appendix: JSON Schema for Variant Metadata ========================================== -.. literalinclude:: variant_schema-0.0.2.json +.. literalinclude:: variant-schema-0.0.2.json :language: json :linenos: :name: variant-json-schema diff --git a/peps/pep-0826/variant_schema-0.0.2.json b/peps/pep-0826/variant-schema-0.0.2.json similarity index 100% rename from peps/pep-0826/variant_schema-0.0.2.json rename to peps/pep-0826/variant-schema-0.0.2.json From c1e8a0d70943a31c789da1bd47a419ed66203b27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Mon, 23 Feb 2026 14:39:32 +0100 Subject: [PATCH 13/14] Update reference implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-0826.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/peps/pep-0826.rst b/peps/pep-0826.rst index 3d9a31470ce..622a666bbe2 100644 --- a/peps/pep-0826.rst +++ b/peps/pep-0826.rst @@ -582,9 +582,7 @@ Reference Implementation ======================== The `variantlib `__ project -contains a reference implementation of a complete variant wheel -solution. It is compliant with this PEP, but also goes beyond it, -providing example solutions to `open issues`_. +contains a reference implementation of this PEP. A client for installing variant wheels is implemented in a `uv branch `__. From 5521023a5f65e9c2f55e3630ac2dee3fa0d31405 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Mon, 23 Feb 2026 14:56:17 +0100 Subject: [PATCH 14/14] How to teach this MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-0826.rst | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/peps/pep-0826.rst b/peps/pep-0826.rst index 622a666bbe2..11b66e1a168 100644 --- a/peps/pep-0826.rst +++ b/peps/pep-0826.rst @@ -577,6 +577,33 @@ 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 ========================