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

Python Enhancement Proposals

PEP 809 – Stable ABI for the Future

Author:
Steve Dower <steve.dower at python.org>
Discussions-To:
Pending
Status:
Draft
Type:
Standards Track
Requires:
703, 793, 697
Created:
19-Sep-2025
Python-Version:
3.15

Table of Contents

Abstract

The Stable ABI as abi3 can no longer be preserved, and requires replacement. abi2026 will be the first replacement, providing resolution of current known incompatibilities, with planned retirement after at least 10 years. The next ABI (for example, abi2031) will have at least five years of overlap with the preceding one.

Long-term stability will be enabled through a mechanism for runtime ABI discovery, allowing extensions to be run with earlier releases that support the same ABI. Changes and additions during the lifespan of an ABI can be added as interfaces, allowing them to be discovered at runtime so that callers can choose suitable fallback behaviour. Currently, such additions prevent extensions from loading at all on earlier runtimes.

The abi3 ABI will be retained in GIL-enabled builds for at least five years, after which time it may be retired (with only abi2026 and later being available). It’s possible that GIL-enabled builds will be retired completely before then. Free-threaded builds do not have abi3, and so their first Stable ABI would be abi2026.

Terminology

This PEP uses “GIL-enabled build” as an antonym to “free-threaded build”, that is, an interpreter or extension built without Py_GIL_DISABLED.

Motivation

The Stable ABI is currently not available for free-threaded builds. Extensions will fail to build when Py_LIMITED_API is defined. Likewise, extensions built for GIL-enabled builds of CPython will fail to load (or crash) on free-threaded builds.

In its acceptance post for PEP 779, the Steering Council stated that it “expects that Stable ABI for free-threading should be prepared and defined for Python 3.15”.

This PEP proposes a Stable ABI that will be compatible with all variants of 3.15 and later, allowing package developers to produce a single build of their extensions.

Background

Python’s Stable ABI, as defined in PEP 384 and PEP 652, provides a way to compile extension modules that can be loaded on multiple minor versions of the CPython interpreter. Several projects use this to limit the number of wheels (binary artefacts) that need to be built and distributed for each release, and/or to make it easier to test with pre-release versions of Python.

With free-threading builds (PEP 703) being on track to eventually become the default (PEP 779), we need a way to make the Stable ABI available to those builds.

To build against the Stable ABI, the extension must use a Limited API, that is, only a subset of the functions, structures, etc. that CPython exposes. The Limited API is versioned, and building against Limited API 3.X yields an extension that is ABI-compatible with CPython 3.X and any later version (though bugs in CPython sometimes cause incompatibilities in practice). Also, the Limited API is not “stable”: newer versions may remove API items that were available in older versions.

This PEP proposes a significant change to versioning of both the Limited API and the Stable ABI. The goal is to enable long-term management of stability and compatibility, while also allowing users of the limited subsets to have access to innovations in later Python releases.

Rationale

The design in this PEP makes several assumptions:

One ABI
A single compiled extension module should support both free-threaded and GIL-enabled builds.
No backwards compatibility
The new limited API will not be supported by CPython 3.14 and below. Projects that need this support can build separate extensions specifically for the 3.14 free-threaded interpreter, and for older stable ABI versions.
API changes are OK
The new Limited API may require extension authors to make significant changes to their code. Projects that cannot do this (yet) can continue using Limited API 3.14, which will yield extensions compatible with GIL-enabled builds only.
No extra configuration
We do not introduce new “knobs” that influence what API is available and what the ABI is compatible with.

Specification

Note that much of the specification is identical to PEP 803, and readers should refer to that proposal for details. The ABI Stability, Build-time Macros and Interfaces API sections are unique to this proposal.

ABI Stability

The Stable ABI will be frozen for a duration of at least 10 years. When a new version of the Stable ABI is frozen, the existing version will continue to be supported for at least 5 years. This allows ample migration time for package maintainers (and other users) to migrate their entire range of supported releases simultaneously. However, if the Python core development team sees no reason to replace the current Stable ABI, freezing a new version may be deferred.

New Stable ABIs are defined using the PEP process, with their name reflecting the release year of the first runtime that supports it.

When a Stable ABI is frozen, the year becomes the name of the ABI. For example, we anticipate that the first ABI under this scheme will be abi2026, and will be supported by all releases until at least 2036. If support is dropped in 2036, then abi2031 would be the migration target, which allows package developers to support at least five years of releases after their own migration.

While frozen, no ABI changes are permitted at all. Additions are not permitted, nor are removals, modifications, or drastic semantic changes. Critically, an extension module compiled against a particular ABI must load successfully (as in, all imported symbols are satisfied on all supported platforms) against any Python version supporting that ABI, earlier or later.

Semantic changes that cannot be detected at runtime via existing compatible ABI are not permitted. That is, the APIs to detect whether a particular behaviour is expected on the current Python release must have been available on all earlier releases that support the ABI.

Opaque PyObject

Version 3.15 of the Limited API will make a number of structures opaque, such that users of them cannot make any assumptions about their size or layout. The details may be found in PEP 803, and the proposal here is identical.

New Export Hook (PEP 793)

Implementation of this PEP requires PEP 793 (PyModExport: A new entry point for C extension modules) to be accepted, providing a new “export hook” for defining extension modules. Using the new hook will become mandatory in Limited API 3.15.

This proposal is identical to that of PEP 803.

Runtime ABI checks

See PEP 803 for details. This proposal is identical.

Build-time macros

We require Py_LIMITED_API to be defined to 0x03ff_YYYY - that is, the high word is a constant 0x03ff, while the low word is the ABI name (year) as a hexadecimal value. While this results in a decimal value that is not the same as the year, we consider that to be unimportant as the value is an arbitrary label and more likely to be specified as a constant (in a cc command line) than a calculated value.

The use of 0x03ff as the constant is intended to allow compatibility with earlier runtimes. The same constant when used with headers only supporting abi3 will select the “most complete” version of ABI3 available in that release. For example, using 0x03ff2026 in 3.15+ would select abi2026, while in 3.10 will select the version of ABI3 that works for 3.10-3.14.

Wheel tags

Wheels should be tagged with the ABI tag abi2026. No changes to Python or platform tags are needed. It is perhaps worth noting that releases tagged for cp314 or earlier will never be compatible with abi2026, as it was not present, and so a wheel tagged py3-abi2026-<plat> is not going to cause a wheel using the new Stable ABI to be loaded by an older release.

New API

Implementing this PEP will make it possible to build extensions that can be successfully loaded on free-threaded Python, but not necessarily ones that are thread-safe without a GIL.

Limited API to allow thread-safety without a GIL – presumably PyMutex, PyCriticalSection, and similar – will be added via the C API working group, or in a follow-up PEP.

Interfaces API

A new interfaces API will be added to Python and the new Limited API. This API is to satisfy the “semantic changes are detectable on all releases” requirement from the ABI Stability section above. That is, consumers [1] will be able to adopt a new API immediately, compile for the Limited API with the latest release, and retain binary compatibility for all releases supporting that ABI.

In short, the primary API is PyObject_GetInterface(), which delegates to a new native-only type slot to fill in a C struct containing either data or function pointers. Because the C struct definition is embedded into the extension, rather than obtained at runtime, an extension module can be aware of later structs while running against releases of Python that do not provide it.

If the call to PyObject_GetInterface requests a struct that is not available on the current version, or is not available for the provided object, the call fails safely. The caller may then use fallback logic (for example, using abstract Python APIs) or abort, based on their preference.

For example, if a new API were to be added during abi2026’s life that allows more efficient access to an int object’s internal data, rather than adding a new API, we would create a new interface: a struct containing a function pointer to copy the data to a new location, and a previously unused index/name for that interface. The caller can call PyObject_GetInterface(int_object, &intf_struct) first; if it succeeds, call (a hypothetical) (*intf_struct.copy_bits)(&intf_struct, dest, sizeof(dest)); if it fails, they can use PyObject_CallMethod(int_object, "to_bytes", ...) to perform the same operation, but less efficiently. The final result of this example is a single extension module that is binary compatible with all releases supporting abi2026 but is more efficient when running against newer releases of Python.

Overview complete, here is the full specification of each new API:

// Abstract API to request an interface for an object (or type).
PyAPI_FUNC(int) PyObject_GetInterface(PyObject *obj, void *intf);

// API to release an interface.
PyAPI_FUNC(int) PyInterface_Release(void *intf);

// Expected layout of the start of each interface. Actual interface structs
// will add additional function pointers or data.
typedef struct PyInterface_Base {
    // sizeof(self), for additional validation that the caller is passing
    // the correct structure.
    Py_ssize_t size;

    // Unique identifier for the struct. Details below.
    uint64_t name;

    // Function to release the struct (e.g. to decref any PyObject fields).
    // Should only be invoked by PyInterface_Release(), not directly.
    int (*release)(struct PyInterface_Base *intf);
} PyInterface_Base;

// Type slot definition for PyTypeObject field.
typedef int (*Py_getinterfacefunc)(PyObject *o, PyInterface_Base *intf);

The unique identifier for the struct is a 64-bit integer defined as a macro (to ensure that compiled extension modules embed the value, rather than trying to discover it at runtime). The top 32 bits are the namespace, and implementers defining their own structs should choose a unique value for themselves. Zero is reserved for CPython.

The interface name is to identify the struct layout, and so any defined object can reuse an interface name from another namespace, provided the struct matches. This is intentional, as it allows third-party types to implement the same interfaces as core types without having to rely on sharing the implementation. To be clear, an interface defined for CPython may be used by other extension modules without changing the name or the name’s namespace.

For example, consider a hypothetical interface to implement PyDict_GetItemString(). The core dict type may do internal optimizations to locate entries by string key, while an external type can use the same interface to do their own optimization. To the caller, it appears to use the same interface, and so the caller is compatible with a broader range of types than if it were using (for example) CPython’s concrete object APIs.

Interface names cannot be removed from headers at any time, and structure definitions can only be removed when all Stable ABI versions supporting them are fully retired. However, objects may stop returning a particular interface if it is no longer recommended or reliable, even if earlier releases did return them. Runtime deprecation warnings may be used if appropriate, no particular rule is specified.

Interface structures are fixed and cannot be changed. When a change is required, a new interface should be defined with a new name. The fields added to a struct for an interface are public API and should be documented. Fields that are not intended for direct use should begin with an underscore, but otherwise cannot be made “private”. Interfaces may provide a mix of data and function pointers, or use strong PyObject * references to avoid race conditions.

After retrieving an interface, the interface must remain valid until it is released, even if the reference to the object is freed. The behaviour of the interface may handle changes to the underlying object however appropriate, but probably should document its choices. It would not be unreasonable to have two similar interfaces that handle these kind of changes differently (e.g. one interface that locks the object for the lifetime of the interface, while another does not).

The process of adding new Limited APIs changes somewhat: rather than having an ABI that grows with each release, new APIs may be added as a real function for when the Limited API is not in use, but should be added as a static inline function for the Limited API. This static inline function should use an interface to detect the functionality at runtime, and include an abstract fallback or suitable exception.

This means that consumers can adopt a new API immediately, compile for the Limited API with the latest release, and retain binary compatibility for all releases that support the same Stable ABI.

At the next Stable ABI freeze, the API can either be promoted to the new Stable ABI/Limited API as a real function, or retained as an interface.

Backwards Compatibility

Limited API 3.15 will not be backwards-compatible with older CPython releases, due to removed structs and functions.

Extension authors who cannot switch may continue to use Limited API 3.14 and below for use on the GIL enabled build.

No changes to abi3 will be made to the GIL enabled build, and all existing symbols will remain available, even though these are no longer available under new Stable ABIs.

Making free-threaded builds the default/only release for CPython will be a backwards-incompatible change, and extension authors will need to have migrated.

Security Implications

None known.

How to Teach This

The native ABI of Python can be described as a periodically updated standard or specification, identified by year, similar to other languages. Any extension module can use this ABI, and declares which ABI they expect as part of their distribution information. Any Python implementation may choose to support a particular ABI version, and any extension also supporting that version should be usable.

Migrating from abi3 to a new ABI may involve source code changes, but can be treated as a one-time task. In many, if not most, cases, source code will be compatible with both abi3 and the new ABI, simplifying production of builds for old releases and current releases. In general, abi3 builds should be built with the oldest supported CPython runtime, and new ABI builds should be built with the latest CPython runtime (or another compatible runtime).

Migrating from one ABI (e.g. abi2026) to the next (e.g. abi2031) should be a manual task. There is enough overlap between ABI updates that most projects only need to support one at a time, and can update all of their builds at once if their own support matrix allows. There is no expectation for package maintainers to immediately support each new ABI.

Forward-and-backward compatibility is ensured by dynamic interface detection. Code using recently added limited API functions will run on older releases, though potentially at lower performance. See the documentation for new functions to find information about any Limited API-specific nuances.

Non-C callers should use the interfaces mechanism directly to get access to new features without artificially limiting their compatibility to newer releases. The names and struct layouts of interfaces are guaranteed stable for all time, though it should not be assumed that an interface will be available for all time, and suitable fallback code (either an alternative implementation or error handling) should be included.

Reference Implementation

See PEP 803 for links to reference implementations for the aspects inherited from that PEP.

The reference implementation of interfaces is zooba/cpython#44.

Rejected Ideas

[See discussion for now.]

Open Issues

[See discussion for now.]

Footnotes


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

Last modified: 2025-09-29 19:41:01 GMT