Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

PEP 780 – ABI features as environment markers

Author:
Klaus Zimmermann <klaus_zimmermann at gmx.de>, Ralf Gommers <ralf.gommers at gmail.com>
Sponsor:
Lysandros Nikolaou <lisandrosnik at gmail.com>
Discussions-To:
Discourse thread
Status:
Draft
Type:
Standards Track
Topic:
Packaging
Created:
21-Mar-2025
Python-Version:
3.14
Post-History:
05-Aug-2024, 26-Mar-2025

Table of Contents

Abstract

This PEP defines using ABI features as environment markers for project dependencies, through a new sys_abi_features environment marker and sys.abi_features attribute in the sys module. PEP 508 (later moved to Dependency specifiers) introduced environment markers to specify dependencies based on rules that describe when the dependency should be used. This PEP extends the environment markers to allow specifying dependencies based on specific ABI features of the Python interpreter. For this, it defines a set of ABI Features and specifies how they are made available via an addition to the Python Standard Library in the form of a new attribute sys.abi_features, as well as for environment markers as a new marker variable, sys_abi_features.

Motivation

In 2015, PEP 508 established environment markers to specify dependencies based on environment conditions. The development of free-threaded CPython [1] has underlined the need for an environment marker to discriminate between different ABI features that the interpreter was built with. For example, currently there is no way to distinguish between a GIL-enabled and a free-threaded CPython interpreter with an environment marker. This leads to real world issues for the adoption of free-threading and its incremental rollout. When a Python package is being made compatible with free-threaded CPython, it also needs all its build and runtime dependencies to be compatible. Capturing the first version of a dependency that is compatible precisely in metadata is currently not possible, and increasing the minimum version of a dependency also for the GIL-enabled build is usually undesirable since it unnecessarily limits compatibility between packages.

Some concrete examples of such issues have been discussed in the Environment marker for free-threading Discourse thread:

  • Cython has (experimental) support for free-threading only in its master branch, and is used by a lot of projects that already publish cp313t wheels. Picking up the wrong Cython version is causing a lot of obscure build failures and runtime crashes. It would be beneficial if the metadata could express that (c.f. Require Cython Pre-release for Free-Threaded Python).
  • CFFI has no support for free-threading yet, and Armin Rigo, one of the maintainers, has stated that it may be a good idea to fork cffi, implement support for free-threading, and only come back to the CFFI project with a single large PR that adds support after the functionality “is reasonably well-tested (either as tests or, better in this case, tested by being in use in various other projects)”. There are a lot of projects that depend on cffi. They are likely fine to start depending on a fork for free-threading only, however depending on a fork for >=3.13 or for all Python versions seems like a much larger ask, and more disruptive for distribution packagers.

While these concrete examples may be addressed later this year by Cython and CFFI making compatible releases, the same issue is going to repeat further up the stack. The free-threading rollout is expected to take several years, and an environment marker for free-threading will make that rollout significantly easier.

Another important ABI feature that is not yet covered by environment markers is the bitness of the interpreter. In most cases, the sys_platform or platform_system markers are enough, because there’s only a single bitness in use per platform. This is not the case on Windows however: both 32-bit and 64-bit Python interpreters are widely used on x86-64 Windows. Not being able to distinguish between the two may be relevant for packages that provide compiled extensions. For example, SciPy does not provide win32 wheels (it isn’t able to due to the lack of a suitable 32-bit compiler toolchain with Fortran support). Those wheels lacking can be awkward especially for projects where SciPy is an optional dependency only. In that case, it would be useful to be able to specify that SciPy is required unless the interpreter is a 32-bit one on Windows (c.f. Require SciPy Unless on 32-bit win32), to avoid failed from-source installations due to the missing wheels.

Rationale

The intention of this PEP is to introduce its core features with minimal impact on the existing ecosystem. The existing grammar proposed in PEP 508 lends itself to a straightforward extension to include the new environment marker.

Adding sys.abi_features to the Python Standard Library

The Python standard library already has mechanisms to provide information about the ABI of the interpreter, like the sys.abiflags attribute and the sysconfig module, but these do not provide all the ABI Features that are relevant for environment markers. The new attribute sys.abi_features is a natural extension to the existing mechanisms, and it is a simple and intuitive way to provide this information, including the information from sys.abiflags.

A Forward Looking View on Free-Threaded Python

PEP 703, the accepted proposal for free threading, states that the rollout of free-threaded Python should be gradual, which has been clarified by the Python Steering Council in the PEP 703 acceptance post to mean a three stage process over multiple releases. It is therefore important to make sure that the mechanisms in this PEP are useable for Python interpreters where either free-threading or non-free-threading could be the default or the only option.

At the time of writing, free-threaded Python is in Phase I: experimental phase. In this phase, there is an acute need for the proposed environment markers to help with the transition to free-threaded Python as package authors gradually add support.

As the number of packages with support increases, and particularly during Phase II: Supported-but-not-default phase, we still anticipate a strong need for the environment markers to help with the transition.

As free-threaded Python enters into Phase III: Default phase, the need for the environment markers will decrease, though at this point it is not clear that the GIL-enabled Python will be completely phased out (it may remain available as a non standard build option). If it persists, the inverse need for the ABI feature detection may arise.

Indeed, in all three phases it may be necessary for package authors to choose specific versions of their dependencies based on the ABI features, with a shift from GIL-enabled as default to free-threading as default over time.

The ABI features are designed with this in mind to guarantee usefulness and simplicity for the foreseeable future in a changing Python ecosystem.

Relation to Other PEPs

This PEP extends environment markers with set semantics for ABI features. PEP 751 includes a similar extension for lock file specific environment markers; although the two have been developed indepedently, they are compatible where they overlap in terms of the new set semantics.

Specification

The keywords “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.

ABI Features

ABI features are intrinsic properties of the Python interpreter, expressed as simple, understandable strings. However, not all features are equally applicable to all Python interpreters or Python versions. For example, the distinction between free-threaded and GIL-enabled interpreters is only relevant for CPython 3.13 onwards, but the bitness of the interpreter is relevant for all interpreters.

All interpreters MUST handle the following ABI features as stated. ABI features that are restricted to particular interpreters MUST NOT be provided by other interpreters. The features are subdivided into groups and for each group there MUST be exactly one feature present, except when the group is marked as optional, in which case there MUST be at most one feature present.

free-threading or gil-enabled (only CPython)
If the Python interpreter is free-threaded, the free-threading feature MUST be present and the gil-enabled feature MUST NOT be present. Otherwise, the gil-enabled feature MUST be present and the free-threading feature MUST NOT be present.
debug (only CPython, optional)
This ABI feature is reserved for the --with-pydebug build of CPython. If the interpreter is a CPython interpreter with Py_DEBUG capabilities, the debug feature MUST be present. On POSIX systems, this corresponds to "d" in sys.abiflags.
32-bit or 64-bit (optional)
The bitness of the interpreter, that is, whether it is a 32-bit or 64-bit build [2]. If the bitness is unknown or neither 32-bit nor 64-bit, this feature MUST NOT be present.

The sys.abi_features Attribute

Making the ABI features available in an easily accessible, expressive, standardized way is useful beyond the scope of environment markers. For example, "32-bit" in sys.abi_features is much more expressive than the current standard test of comparing sys.maxsize with 2**32, which can be found more than ten thousand times on GitHub. If one wants to determine whether the interpreter is a debug build, there is currently no standardized, cross platform way to do so, and sys.abiflags is not available on Windows. Hence, the ABI Features listed above are added to the Python standard library.

Since they are all the result of compile time choices describing basic features of the interpreter, the most intuitive place to put them is in sys. Since there is no intrinsic order, nor a possibility for duplication, they are added as a frozenset of strings.

All Python interpreters MUST provide the sys.abi_features attribute as a frozenset of strings, which MUST contain only the ABI Features that are defined in this PEP or in a subsequent PEP.

An example value would be sys.abi_features == frozenset({"free-threading", "debug", "32-bit"}) on a free-threaded debug build for win32.

The sys_abi_features Environment Marker

To make ABI features available in dependency specifications, a new environment marker variable, sys_abi_features, is added to the format of dependency specifiers with the same semantics as the sys.abi_features attribute proposed above.

To do this, we need to extend the grammar laid out in PEP 508 and maintained in the Dependency specifiers and document the possible values.

The grammar is extended to include the sys_abi_features marker variable by augmenting the definition of env_var as follows:

env_var       = ('python_version' | 'python_full_version' |
                 'os_name' | 'sys_platform' | 'platform_release' |
                 'platform_system' | 'platform_version' |
                 'platform_machine' | 'platform_python_implementation' |
                 'implementation_name' | 'implementation_version' |
                 'sys_abi_features' |
                 'extra' # ONLY when defined by a containing layer
                 )

Like the grammar, also the overview table of environment markers in Dependency specifiers is augmented to add the following row:

Marker Python equivalent Sample values
sys_abi_features sys.abi_features [3] frozenset(), frozenset({'free-threading', '64-bit'}), frozenset({'gil-enabled', 'debug', '32-bit'})

With these additions, ABI features can be used in dependency specifications via the in operator to test for the presence of a feature, or the not in operator to test for the absence of a feature.

Note that the presence of sys.abi_features in the Python standard library makes implementation particularly easy for new Python versions, but its absence in older versions does not prevent the implementation of the new environment markers, as demonstrated in the Reference Implementation.

Examples

Require Cython Pre-release for Free-Threaded Python

To require a pre-release of Cython only for a free-threaded Python interpreter, the following dependency specification can be used:

cython >3.1.0a1; "free-threading" in sys_abi_features
cython ==3.0.*; "free-threading" not in sys_abi_features

Require SciPy Unless on 32-bit win32

To require SciPy unless on a 32-bit interpreter on Windows, the following dependency specification can be used:

scipy; platform_system != "Windows" or "32-bit" not in sys_abi_features

Require NumPy for a Free-Threaded Interpreter With Debugging Capabilities

To require NumPy only for a free-threaded interpreter with debugging capabilities, the following dependency can be used:

numpy; "free-threading" in sys_abi_features and "debug" in sys_abi_features

Backwards Compatibility

This is a pure extension to the existing environment markers and does not affect existing environment markers or dependency specifications, hence there are no direct backwards compatibility concerns.

However, the introduction of the feature has implications for a number of ecosystem tools, especially those which attempt to support examination of data in pyproject.toml and requirements.txt.

Audit and Update Tools

A wide range of tools understand Python dependency data as expressed in requirements.txt files. (e.g., Dependabot, Tidelift, etc)

Such tools inspect dependency data and, in some cases, offer tool-assisted or fully automated updates. It is our expectation that no such tools would support the new environment markers at first, and broad ecosystem support could take many months or even some number of years to arrive.

As a result, users of the new environment markers would experience a degradation in their workflows and tool support at the time that they start using them. This is true of any new standard for where and how dependency data are encoded.

Security Implications

This PEP introduces new syntax for specifying dependency information in projects. However, it does not introduce newly specified mechanisms for handling or resolving dependencies.

It therefore does not carry security concerns other than those inherent in any tools which may already be used to install dependencies—i.e. malicious dependencies may be specified here, just as they may be specified in requirements.txt files.

How to Teach This

The use of environment markers is well established and communicated chiefly in Dependency specifiers. The new environment marker can be introduced in the same document. Additionally, both for package authors and users, free-threading specific guidance can be provided at the Python free-threading guide. The new sys.abi_features attribute will be documented in the Python standard library documentation.

Reference Implementation

The reference implementation for the sys.abi_features attribute can be found in Add abi_features to sys.

The reference implementation for the environment markers is available in a fork of the packaging library at Environment markers for ABI features.

A demonstration package is also available.

Since pip uses a vendored copy of packaging internally, we also provide a patched version of pip, which replaces the vendored packaging with the reference implementation linked above.

Rejected Ideas

Extension Mechanism

In an early discussion of the topic (Environment marker for free-threading), the idea of a general extension mechanism for environment markers was brought up. While it is appealing to forego a whole PEP process should the need for new environment markers arise in the future, there are two main challenges. First, a completely dynamic mechanism would present difficulties for tools that rely on static analysis of dependency specifications.

This means that even if a dynamic mechanism were to be adopted, new environment markers would likely still need to be spelled out in a PEP.

Second, the introduction of a dynamic mechanism would require a more complex implementation in the packaging library, which would be a significant departure from the current approach.

Having said that, the new sys.abi_features attribute provides a natural extension point for any new ABI features, even if specific to a subset of interpreters, should the need arise to add such new features with a subsequent PEP.

Open Issues

Other Environment Markers

If other environment markers are needed right now, this PEP could be extended to include them.

Other Tooling

The reference implementation is based on the packaging library and pip. We have confirmed that this allows for building and installing packages with several build backends. It is possible that other tools should be added to the reference implementation.

Footnotes

Acknowledgements

Thanks to Filipe Laíns for the suggestion of the abi_features attribute and to Stephen Rosen for the Backwards Compatibility section of PEP 735, which served as a template for the corresponding section in this PEP.


Source: https://github.com/python/peps/blob/main/peps/pep-0780.rst

Last modified: 2025-03-26 10:59:21 GMT