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
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 oncffi
. 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
orgil-enabled
(only CPython)- If the Python interpreter is free-threaded, the
free-threading
feature MUST be present and thegil-enabled
feature MUST NOT be present. Otherwise, thegil-enabled
feature MUST be present and thefree-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 withPy_DEBUG
capabilities, thedebug
feature MUST be present. On POSIX systems, this corresponds to"d" in sys.abiflags
. 32-bit
or64-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.
Copyright
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.
Source: https://github.com/python/peps/blob/main/peps/pep-0780.rst
Last modified: 2025-03-26 10:59:21 GMT