From 8789ab5c8b085101f070f985302fe3db4b53b791 Mon Sep 17 00:00:00 2001 From: Jonathan Dekhtiar Date: Mon, 1 Dec 2025 20:26:42 -0500 Subject: [PATCH 01/19] WHLNXT render --- .github/workflows/render.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/render.yml b/.github/workflows/render.yml index afdd64ecd60..c508050407b 100644 --- a/.github/workflows/render.yml +++ b/.github/workflows/render.yml @@ -50,7 +50,7 @@ jobs: - name: Deploy to GitHub pages # This allows CI to build branches for testing - if: (github.ref == 'refs/heads/main') && (matrix.python-version == '3.x') + if: matrix.python-version == '3.x' uses: JamesIves/github-pages-deploy-action@v4 with: folder: build # Synchronise with Makefile -> BUILDDIR From 5265483c180eae9bd470eb21a92d8f7d34fb3a95 Mon Sep 17 00:00:00 2001 From: Jonathan Dekhtiar Date: Tue, 9 Dec 2025 18:03:54 -0500 Subject: [PATCH 02/19] Skip build if branch is `norender*` --- .github/workflows/render.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/render.yml b/.github/workflows/render.yml index c508050407b..2e27db58edb 100644 --- a/.github/workflows/render.yml +++ b/.github/workflows/render.yml @@ -50,7 +50,7 @@ jobs: - name: Deploy to GitHub pages # This allows CI to build branches for testing - if: matrix.python-version == '3.x' + if: (!startsWith(github.ref, 'refs/heads/norender')) && (matrix.python-version == '3.x') uses: JamesIves/github-pages-deploy-action@v4 with: folder: build # Synchronise with Makefile -> BUILDDIR From 7a0ffa4bae4ad5cd990f49846e0735b2ca31e402 Mon Sep 17 00:00:00 2001 From: Jonathan DEKHTIAR Date: Tue, 10 Feb 2026 12:59:24 -0500 Subject: [PATCH 03/19] [Github CI] Restore Upstream Behavior - only main (#36) * Github CI - Restore to Upstream * RTD Project Slug --- .github/workflows/documentation-links.yml | 2 +- .github/workflows/render.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/documentation-links.yml b/.github/workflows/documentation-links.yml index 43ae724a839..d0b63067309 100644 --- a/.github/workflows/documentation-links.yml +++ b/.github/workflows/documentation-links.yml @@ -20,5 +20,5 @@ jobs: steps: - uses: readthedocs/actions/preview@v1 with: - project-slug: "pep-previews" + project-slug: "wheelnext-peps" single-version: "true" diff --git a/.github/workflows/render.yml b/.github/workflows/render.yml index 2e27db58edb..afdd64ecd60 100644 --- a/.github/workflows/render.yml +++ b/.github/workflows/render.yml @@ -50,7 +50,7 @@ jobs: - name: Deploy to GitHub pages # This allows CI to build branches for testing - if: (!startsWith(github.ref, 'refs/heads/norender')) && (matrix.python-version == '3.x') + if: (github.ref == 'refs/heads/main') && (matrix.python-version == '3.x') uses: JamesIves/github-pages-deploy-action@v4 with: folder: build # Synchronise with Makefile -> BUILDDIR From 11b31f6df8429e0313ae360708d7ed86cdc920ca Mon Sep 17 00:00:00 2001 From: Jonathan Dekhtiar Date: Tue, 10 Feb 2026 13:16:26 -0500 Subject: [PATCH 04/19] [RTD] Doc Link --- .github/workflows/documentation-links.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/documentation-links.yml b/.github/workflows/documentation-links.yml index d0b63067309..4beb9633d8a 100644 --- a/.github/workflows/documentation-links.yml +++ b/.github/workflows/documentation-links.yml @@ -16,7 +16,6 @@ concurrency: jobs: documentation-links: runs-on: ubuntu-latest - if: github.event.repository.fork == false steps: - uses: readthedocs/actions/preview@v1 with: From f2835db9c9a7ce4e0ebb4e8e0fff8370babea35a 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 05/19] PEP xxxx: Wheel Variants: Providers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is the second PEP split off PEP 817. Its focus is on 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. Compared to PEP 817, the provider metadata has been largely simplified by removing all the bits deemed not strictly necessary, and provider plugins have been made opt-in (with provisions for tools to make some of them opt-out, at their leisure). The recommendations for governance of opt-out providers and building variant wheels will follow in subsequent PEPs. Signed-off-by: Michał Górny --- peps/pep-9999.rst | 743 ++++++++++++++++++ .../pep-9999/appendix-variant-json-schema.rst | 11 + peps/pep-9999/variant-schema-0.2.0.json | 176 +++++ 3 files changed, 930 insertions(+) create mode 100644 peps/pep-9999.rst create mode 100644 peps/pep-9999/appendix-variant-json-schema.rst create mode 100644 peps/pep-9999/variant-schema-0.2.0.json diff --git a/peps/pep-9999.rst b/peps/pep-9999.rst new file mode 100644 index 00000000000..af242cf2b1a --- /dev/null +++ b/peps/pep-9999.rst @@ -0,0 +1,743 @@ +PEP: 9999 +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 +======== + +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: + +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`. + + +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 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 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 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 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 by 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. + +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 an +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.2.0`` 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:`pep9999-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. Its set of keys MUST match the set of values in +the ``default-priorities.namespace`` list of variant metadata. + +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]]``: The dictionary of static + variant compatibility data. The keys correspond to feature names, + while the values are the ordered list of compatible feature 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. At least one dependency + MUST remain present in every possible environment after filtering. + Additionally, if ``plugin-api`` is not specified, the first dependency + present after filtering MUST always evaluate to the same API endpoint. + +If both keys 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. + + +Example +''''''' + +.. code:: json5 + + { + // The schema URL will be replaced with the final URL on packaging.python.org + "$schema": "https://variants-schema.wheelnext.dev/v0.0.3.json", + + "default-priorities": { + // MUST list all namespaces used. + "namespace": ["x86_64", "aarch64", "blas_lapack"], + + // other fields same as in PEP 825 + "feature": {}, + "property": {} + }, + + "providers": { + // MUST list all namespaces used. + "aarch64": { + // Specifies provider plugin package. REQUIRED since there is no + // "static-properties" -- plugin will be queried at install time. + "requires": ["provider-variant-aarch64 >=0.0.1"], + // Overrides plugin API endpoint. OPTIONAL; without it, the API + // endpoint "provider_variant_aarch64" would be used. + "plugin-api": "provider_variant_aarch64.plugin:AArch64Plugin" + }, + "blas_lapack": { + // Specifies compatible properties. No install-time querying is + // necessary. + "static-properties": { + // Values are ordered -- by default, openblas variant is preferred + // over mkl, and accelerate over the other two. + "library": ["accelerate", "openblas", "mkl"] + }, + // OPTIONAL: unused since "static-properties" are available. + "requires": ["blas-lapack-variant-provider"] + }, + "x86_64": { + // Specifies provider plugin package. REQUIRED since there is no + // "static-properties" -- plugin will be queried at install time. + "requires": [ + "provider-variant-x86-64 >=0.0.1; python_version >= '3.12'", + "legacy-provider-variant-x86-64 >=0.0.1; python_version < '3.12'" + ], + // Overrides plugin API endpoint. REQUIRED since "requires" may + // evaluate to two different packages depending on Python version. + "plugin-api": "provider_variant_x86_64.plugin:X8664Plugin" + } + }, + + "variants": { + // same as in PEP 825 + } + } + + +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: + +.. 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 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 +'''''''''''''''''''''' + +.. 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 +========= + +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 +<#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 +======================= + +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. + +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-if`` key has been removed from `provider information`_, + as it was deemed redundant. + - The ``static-properties`` table has been moved into `provider + information`_. + + +Appendices +========== + +- :ref:`pep9999-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-9999/appendix-variant-json-schema.rst b/peps/pep-9999/appendix-variant-json-schema.rst new file mode 100644 index 00000000000..b5e05c1f4b2 --- /dev/null +++ b/peps/pep-9999/appendix-variant-json-schema.rst @@ -0,0 +1,11 @@ +:orphan: + +.. _pep9999-variant-json-schema: + +Appendix: JSON Schema for Variant Metadata +========================================== + +.. literalinclude:: variant-schema-0.2.0.json + :language: json + :linenos: + :name: variant-json-schema diff --git a/peps/pep-9999/variant-schema-0.2.0.json b/peps/pep-9999/variant-schema-0.2.0.json new file mode 100644 index 00000000000..a1b1eec162e --- /dev/null +++ b/peps/pep-9999/variant-schema-0.2.0.json @@ -0,0 +1,176 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://variants-schema.wheelnext.dev/peps/826/v0.2.0.json", + "title": "Variant metadata, v0.2.0", + "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", + "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" + ] + }, + "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", + "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": [ + "$schema", + "default-priorities", + "providers", + "variants" + ], + "additionalProperties": false, + "uniqueItems": true +} From 637390e020f649d6829e0aa1ae006395651a54af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Mon, 2 Mar 2026 14:48:59 +0100 Subject: [PATCH 06/19] Update headers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9999.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/peps/pep-9999.rst b/peps/pep-9999.rst index af242cf2b1a..47fda1afe55 100644 --- a/peps/pep-9999.rst +++ b/peps/pep-9999.rst @@ -11,12 +11,13 @@ Author: Jonathan Dekhtiar , Barry Warsaw , Donald Stufft , Andy R. Terrel -Discussions-To: https://discuss.python.org/t/pep-817-split-wheel-variants-package-format/106196 +Discussions-To: Pending Status: Draft Type: Standards Track Topic: Packaging -Created: 17-Feb-2026 -Post-History: `17-Feb-2026 `__ +Requires: 825 +Created: 02-Mar-2026 +Post-History: Pending Abstract ======== From a63a74962121bd8a741d7041f5a909031a6ee195 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Mon, 2 Mar 2026 17:06:46 +0100 Subject: [PATCH 07/19] Update schema URL in example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9999.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/peps/pep-9999.rst b/peps/pep-9999.rst index 47fda1afe55..b4edf8ee8f1 100644 --- a/peps/pep-9999.rst +++ b/peps/pep-9999.rst @@ -200,7 +200,7 @@ Example { // The schema URL will be replaced with the final URL on packaging.python.org - "$schema": "https://variants-schema.wheelnext.dev/v0.0.3.json", + "$schema": "https://variants-schema.wheelnext.dev/peps/9999/v0.2.0.json", "default-priorities": { // MUST list all namespaces used. From 3bb89a0989e3064f4c672208e0c21fd63d71d65a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Mon, 2 Mar 2026 18:14:18 +0100 Subject: [PATCH 08/19] Add a preface to the variant metadata example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9999.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/peps/pep-9999.rst b/peps/pep-9999.rst index b4edf8ee8f1..7d29e49286b 100644 --- a/peps/pep-9999.rst +++ b/peps/pep-9999.rst @@ -196,6 +196,9 @@ keys: Example ''''''' +The ``foo-1.2.3-variants.json`` example from :pep:`825` can be extended +in the following way: + .. code:: json5 { From 9ad6dd15b3f1c7b822551ec6be97bae320f304e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Mon, 2 Mar 2026 20:18:53 +0100 Subject: [PATCH 09/19] Make `requires` and `static-properties` mutually exclusive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9999.rst | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/peps/pep-9999.rst b/peps/pep-9999.rst index 7d29e49286b..e7b6d75422a 100644 --- a/peps/pep-9999.rst +++ b/peps/pep-9999.rst @@ -157,8 +157,8 @@ the ``default-priorities.namespace`` list of variant metadata. 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: +Exactly one of the following keys MUST be present in the provider +information dictionary: - ``static-properties: dict[str, list[str]]``: The dictionary of static variant compatibility data. The keys correspond to feature names, @@ -177,21 +177,25 @@ dictionary: Additionally, if ``plugin-api`` is not specified, the first dependency present after filtering MUST always evaluate to the same API endpoint. -If both keys are provided, the ``requires`` key MUST be ignored. - A provider information dictionary MAY additionally contain the following -keys: +key: - ``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. +If ``requires`` key is present, it MAY additionally contain the +following key: + - ``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. +It is invalid to specify ``requires`` or ``plugin-api`` if +``static-properties`` are present. + Example ''''''' @@ -232,8 +236,6 @@ in the following way: // over mkl, and accelerate over the other two. "library": ["accelerate", "openblas", "mkl"] }, - // OPTIONAL: unused since "static-properties" are available. - "requires": ["blas-lapack-variant-provider"] }, "x86_64": { // Specifies provider plugin package. REQUIRED since there is no From 987396da9a9fb71a221df271f1d458e60f201e77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Mon, 2 Mar 2026 20:23:54 +0100 Subject: [PATCH 10/19] Update the example to demonstrate implicit `plugin-api` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9999.rst | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/peps/pep-9999.rst b/peps/pep-9999.rst index e7b6d75422a..e941fa1d090 100644 --- a/peps/pep-9999.rst +++ b/peps/pep-9999.rst @@ -184,8 +184,8 @@ key: to ``false``. If it is ``true``, the provider is disabled by default and needs to be enabled explicitly. -If ``requires`` key is present, it MAY additionally contain the -following key: +If the ``requires`` key is present, the dictionary MAY additionally +contain the following key: - ``plugin-api: str``: The API endpoint for the plugin. If it is specified, it MUST be an object reference as explained in the `API @@ -223,10 +223,9 @@ in the following way: "aarch64": { // Specifies provider plugin package. REQUIRED since there is no // "static-properties" -- plugin will be queried at install time. - "requires": ["provider-variant-aarch64 >=0.0.1"], - // Overrides plugin API endpoint. OPTIONAL; without it, the API - // endpoint "provider_variant_aarch64" would be used. - "plugin-api": "provider_variant_aarch64.plugin:AArch64Plugin" + "requires": ["provider-variant-aarch64 >=0.0.1"] + // "plugin-api" is OPTIONAL here. It is inferred from "requires": + // "plugin-api": "provider_variant_aarch64" }, "blas_lapack": { // Specifies compatible properties. No install-time querying is From 5926acd65534fb59a49eaaba7b7a03e35da49de5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Mon, 2 Mar 2026 20:32:38 +0100 Subject: [PATCH 11/19] Require at least one item in `requires` for simplicity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9999.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/peps/pep-9999.rst b/peps/pep-9999.rst index e941fa1d090..9a1c59782bd 100644 --- a/peps/pep-9999.rst +++ b/peps/pep-9999.rst @@ -167,7 +167,7 @@ information dictionary: 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 +- ``requires: list[str]``: A list of one 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 From 1282440568c0128b2d9115c6c9028ccbcdd6fa7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Tue, 3 Mar 2026 11:06:38 +0100 Subject: [PATCH 12/19] Add `install-time` removal to changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9999.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/peps/pep-9999.rst b/peps/pep-9999.rst index 9a1c59782bd..8592d1d4c31 100644 --- a/peps/pep-9999.rst +++ b/peps/pep-9999.rst @@ -729,8 +729,8 @@ Change History - 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 ``enable-if`` and ``install-time`` keys have been removed from + `provider information`_, as they were deemed redundant. - The ``static-properties`` table has been moved into `provider information`_. From 6ae8186fc4c3f5464635709436a4779e66cfc849 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Mon, 9 Mar 2026 14:11:53 +0100 Subject: [PATCH 13/19] Remove class from example for simplicity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9999.rst | 48 +++++++++++++++++++++++------------------------ 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/peps/pep-9999.rst b/peps/pep-9999.rst index 8592d1d4c31..096347dfc9d 100644 --- a/peps/pep-9999.rst +++ b/peps/pep-9999.rst @@ -436,31 +436,29 @@ Example implementation ... # 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, - ), - ] + 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 From 4701edf92b80e209cd8c271dd83a7ea4015bc087 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Mon, 9 Mar 2026 19:59:00 +0100 Subject: [PATCH 14/19] Explicitly forbid `abi_dependency` in priorities too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9999.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/peps/pep-9999.rst b/peps/pep-9999.rst index 096347dfc9d..18e2c4b8827 100644 --- a/peps/pep-9999.rst +++ b/peps/pep-9999.rst @@ -485,8 +485,9 @@ 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. +MUST NOT be listed in the `provider information`_ dictionary nor in the +``default-priorities.namespace`` 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 From b0c3a898a86cc04ddefb40fda73105a27680236d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Wed, 11 Mar 2026 17:13:25 +0100 Subject: [PATCH 15/19] Apply suggestions from code review Co-authored-by: Ralf Gommers --- peps/pep-9999.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/peps/pep-9999.rst b/peps/pep-9999.rst index 18e2c4b8827..afce45d6ee6 100644 --- a/peps/pep-9999.rst +++ b/peps/pep-9999.rst @@ -50,7 +50,7 @@ satisfying three use cases: 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. + users 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 @@ -91,7 +91,7 @@ rules apply, in order: 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 compatible +2. A variant provider's 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. @@ -271,7 +271,7 @@ 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 +published in the same indexes as the packages using them. In particular, packages published to PyPI MUST NOT rely on plugins that need to be installed from other indexes. From 55104eb6b1c0109108cc46e42766e23c02de44d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Wed, 11 Mar 2026 18:07:11 +0100 Subject: [PATCH 16/19] Address some review comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9999.rst | 43 ++++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/peps/pep-9999.rst b/peps/pep-9999.rst index afce45d6ee6..f1c67e935e9 100644 --- a/peps/pep-9999.rst +++ b/peps/pep-9999.rst @@ -38,7 +38,7 @@ 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. +system. This PEP aims to fill this gap. The PEP is specifically aiming to make variant wheels suitable for satisfying three use cases: @@ -100,11 +100,11 @@ rules apply, in order: said list, the tools MUST use it. Otherwise, proceed to step 4. 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. + provided, the variant provider metadata MUST specify a Python package + that provides a variant provider plugin. The tools MAY choose to + vendor or reimplement plugin packages. If that is the case, they + SHOULD 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 by default. They SHOULD provide a way for the user to trust @@ -262,28 +262,29 @@ 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. +MUST use the same provider for a given namespace. 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 as 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. +Except for namespaces reserved as part of this PEP and variant providers +that include static lists of compatible features and their values as +part of their metadata, installable Python packages MUST be provided for +plugins. The entire dependency tree of such packages MUST be installable +using non-variant wheels, and variant wheels MUST NOT be used while +installing them. 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. +reimplemented by tools needing them. If that is the case, the API +defined in this section does not apply to these implementations. +However, the reimplementer is responsible for ensuring consistent +behavior with the published provider. A plugin implemented as Python package exposes callables that are called via: @@ -575,7 +576,7 @@ 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 +options. For example, build frontends may prefer reusing the isolated build environment for this. The ``requires`` and ``plugin-api`` keys follow the precedent of From f58559d3bb9ee57c9e95942f9bf2cc849bb16191 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Wed, 11 Mar 2026 18:37:24 +0100 Subject: [PATCH 17/19] Clarify what kind of tools, and what the algorithm's purpose is MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9999.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/peps/pep-9999.rst b/peps/pep-9999.rst index f1c67e935e9..9398de5ff5f 100644 --- a/peps/pep-9999.rst +++ b/peps/pep-9999.rst @@ -82,8 +82,12 @@ 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: +dictionary in `variant metadata`_. + +Installers and other tools that need to determine whether a variant is +compatible with the system MUST follow the specified algorithm in order +to obtain the list of compatible features and feature values for every +namespace: 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 From 0a9cf5a892d03a374d8a527387722bceb9fd9a0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Thu, 12 Mar 2026 14:54:26 +0100 Subject: [PATCH 18/19] Address more feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9999.rst | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/peps/pep-9999.rst b/peps/pep-9999.rst index 9398de5ff5f..4b528875be5 100644 --- a/peps/pep-9999.rst +++ b/peps/pep-9999.rst @@ -97,11 +97,12 @@ namespace: 2. A variant provider's 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 static lists exclusively. Otherwise, proceed to step 3. 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. + said list, the tools MUST use it exclusively. Otherwise, proceed to + step 4. 4. If no static list of compatible features and their values is provided, the variant provider metadata MUST specify a Python package @@ -126,8 +127,8 @@ Variant metadata ---------------- This PEP extends the metadata introduced in :pep:`825` with an -additional ``providers`` key. Therefore, the metadata has the following -structure: +additional ``providers`` key, described in the `Provider information`_ +subsection. Therefore, the metadata has the following structure: .. code:: text @@ -153,9 +154,10 @@ included in the Appendix of this PEP. The schema is available in Provider information '''''''''''''''''''' -``providers`` is a dictionary, the keys are namespaces, the values are +Variant metadata must feature a ``providers`` key. It corresponds to a +dictionary, in which the keys are namespaces, the values are dictionaries with provider information. It specifies how to install and -use variant providers. Its set of keys MUST match the set of values in +use variant providers. Its set of keys MUST match the set of values in the ``default-priorities.namespace`` list of variant metadata. The use of provider information is described in the `Providers`_ and From 7b369ae786c304dc3dac1941372b8f5f60ca83ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Thu, 12 Mar 2026 15:00:47 +0100 Subject: [PATCH 19/19] Try clarifying `plugin-api` defaults MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9999.rst | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/peps/pep-9999.rst b/peps/pep-9999.rst index 4b528875be5..a7e92567edc 100644 --- a/peps/pep-9999.rst +++ b/peps/pep-9999.rst @@ -178,10 +178,11 @@ information dictionary: 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. At least one dependency + markers evaluate to false are filtered out. At least one dependency MUST remain present in every possible environment after filtering. - Additionally, if ``plugin-api`` is not specified, the first dependency - present after filtering MUST always evaluate to the same API endpoint. + Additionally, if ``plugin-api`` is not specified explicitly, the first + dependency that remains after filtering MUST always evaluate to the + same default ``plugin-api`` value. A provider information dictionary MAY additionally contain the following key: @@ -195,9 +196,10 @@ contain the following key: - ``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. + endpoint`_ section. If it is not specified, a default value is + determined by normalizing the package name from the first dependency + specifier in ``requires`` and then replacing all ``-`` characters with + ``_`` in the normalized package name. It is invalid to specify ``requires`` or ``plugin-api`` if ``static-properties`` are present.