PEP: 803
Title: "abi3t": Stable ABI for Free-Threaded Builds
Author: Petr Viktorin <encukou@gmail.com>, Nathan Goldbaum <nathan.goldbaum@gmail.com>
Discussions-To: https://discuss.python.org/t/106181
Status: Final
Type: Standards Track
Requires: 703, 793, 697
Created: 19-Aug-2025
Python-Version: 3.15
Post-History: `08-Sep-2025 <https://discuss.python.org/t/103628>`__,
              `20-Nov-2025 <https://discuss.python.org/t/104976>`__,
              `16-Feb-2026 <https://discuss.python.org/t/106181>`__,
Resolution: `30-Mar-2026 <https://discuss.python.org/t/106181/26>`__

.. canonical-doc:: :ref:`py3.15:stable-abi` and :ref:`py3.15:abi3t-migration-howto`


Abstract
========

Add a new variant of the Stable ABI, called “Stable ABI for Free-Threaded
Python” (or ``abi3t`` for short).

``abi3t`` will be based on the existing Stable ABI (``abi3``), but make the
:c:type:`PyObject` structure opaque.
This will require users to migrate to new API for common tasks like defining
modules and most classes.

In practice, ``abi3t`` 3.15 will be compatible with ``abi3`` 3.15.
Extension authors are encouraged to explicitly compile for both ABIs at once,
and signal compatibility using the wheel tag ``abi3.abi3t``.


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 for the Stable ABI on
free-threaded Python (that is, when both :c:macro:`Py_LIMITED_API` and
:c:macro:`Py_GIL_DISABLED` preprocessor macros are defined).
Extensions built for GIL-enabled builds of CPython will fail to load
(or crash) on free-threaded builds.

In its `acceptance post <https://discuss.python.org/t/84319/123>`__
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 the Stable ABI for free-threading.


Background & Summary
--------------------

Python's Stable ABI (``abi3`` for short), 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
:ref:`wheels <packaging:binary-distribution-format>` (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 a Stable ABI available
to those builds.

The current Stable ABI is versioned, and an extension built for Stable ABI
3.X is ABI-compatible with CPython 3.X and *any* later version
(though bugs in CPython sometimes cause incompatibilities in practice).

However, this forward compatibility is only guaranteed for
a subset of the API that CPython exposes (functions, structures, etc.).
Extensions that target the Stable ABI must limit themselves to this subset,
called the :ref:`Limited API <py3.15:limited-api-list>`.
When the "opt-in" preprocessor macro ``Py_LIMITED_API`` is defined,
CPython headers will expose the Limited API only

This PEP proposes a *stable ABI for free-threading builds*
(``abi3t`` for short), which includes additional API limitations needed to
compile extensions compatible with both GIL-enabled and free-threaded builds
of CPython 3.15+,
a corresponding macro to opt in to these limitations, and naming/tagging
schemes that extensions should use to signal such compatibility.


Ecosystem maintainers want decreased maintenance burden
-------------------------------------------------------

A major advantage of the limited API and stable ABI wheels is that new
Python versions are supported on the day of release. Without stable ABI
wheels, maintainers are left with a choice between closely following CPython
releases and producing wheels during CPython beta periods or dealing with
inevitable user requests for support for new CPython versions.


Cryptography
^^^^^^^^^^^^

The cryptography project shipped 48 wheel files with their `most recent release
<https://pypi.org/project/cryptography/46.0.5/#files>`_. Cryptography is
somewhat unusual in that they ship 14 wheels each for both the ``cp38`` and
``cp311`` stable ABIs to enable optimizations available in newer limited API
versions. They also ship 14 additional ``cp314t`` wheels and 6 wheels for
PyPy. If there is no free-threaded stable ABI, then with Python 3.15,
cryptography will be using roughly the same amount of space on PyPI to support
two versions of the free-threaded build as *all* non-EOL versions of the
GIL-enabled build.

Cryptography maintainer Alex Gaynor `expressed a desire
<https://discuss.python.org/t/making-pyobject-opaque-in-the-limited-api/77206/2>`_
on Discourse for a free-threaded stable ABI:

    Just to state this explicitly from the PyCA maintainers perspective, as
    long as we have O(1) builds, that’s ok. What we can’t/won’t do is O(n)
    where we need new builds for every Python release.

When one of the PEP authors asked Alex in the ``#pyca`` Libera IRC channel for
his current opinion, he said:

    One other thing I'll note that's *really* valuable about ``abi3`` is that it
    means our old wheels keep working for new Python versions. If we have
    per-Python release wheels, we have to do a bunch of work at various points
    in the python release cycle (including potentially backport releases to add
    new wheels, if we're not otherwise planning a release at that time).

    As maintainers, we *really* like to structure our work to avoid being "on
    the clock" like that.


moocore
^^^^^^^

The `moocore project ships <https://pypi.org/project/moocore/0.2.0/#files>`_
seven ``abi3`` wheels. When the topic of adding support for
the free-threaded build `came up on the moocore issue tracker
<https://github.com/multi-objective/moocore/issues/37>`_, maintainer Manuel
López-Ibáñez let the person reporting the issue know that:

    I don't want to build for 3.14 free-threading unless you really need it.

Later, after discovering the tracking issue for supporting the limited API on the
free-threaded build, he commented:

    By the way, python/cpython#111506 is about extending the stable ABI to
    support free-threaded Python. If they do that, then the builds of moocore
    will work in both classical and free-threaded Python versions, without
    needing to build new wheels for each Python version.

    [...]

    I will revisit this again once Python 3.15 is released. Hopefully the ABI
    will be stable (or even better, free-threading will be the default).

Pydantic
^^^^^^^^

Pydantic maintainer David Hewitt `observed
<https://github.com/python/peps/pull/4831#issuecomment-3972183807>`_:

    Pydantic distributes wheels for a native core built using Rust & PyO3. The
    latest release of `pydantic-core distributed
    <https://pypi.org/project/pydantic_core/2.42.0/#files>`_ 112 wheels and
    this number is set to grow as more environments are to be added
    (e.g. Android, iOS, wasm). Pydantic has historically not distributed using
    the stable ABI because the feature set was too immature. Much Pydantic
    functionality interacts with Python objects via the C API in hot loops so
    performance is key concern. As the stable ABI matures it will be ideal for
    Pydantic to switch tier 2 platforms to the stable ABI (and perhaps
    eventually tier 1 platforms too), which will significantly reduce the
    number of wheels to build, test, and distribute.

    I would like to highlight that if free-threading does not adopt a stable
    ABI, all the benefits above will be lost when free-threading becomes the
    default and only build (which seems the expected long-term plan).


SciPy
^^^^^

The SciPy project uploaded 60 version-specific wheel files for its `last release
<https://pypi.org/project/scipy/1.17.1/#files>`_ to support four different
CPython versions. They do not upload wheels for PyPy.

When asked about this proposal, SciPy steering council chair `Ralf Gommers said
<https://github.com/python/peps/pull/4831#issuecomment-3968143777>`_:

    SciPy and a number of other projects in the scientific Python ecosystem
    are quite interested in starting to use the Stable ABI, in particular to
    reduce the maintenance load of `providing more wheels
    <https://pypackaging-native.github.io/meta-topics/user_expectations_wheels/>`_.
    With recent CPython, Cython and NumPy releases, this now seems
    possible. The performance costs seem acceptable and small, although we'll
    only really build confidence in that assessment after having made the
    switch.

By providing a new free-threaded stable ABI in Python 3.15, SciPy will not have
to consider the lack of a stable ABI on the free-threaded build as the project
considers switching to stable ABI wheels.


Bindings generators
-------------------

Both moocore and cryptography use bindings generators to interface with the C
API. Cryptography uses PyO3 and CFFI while moocore uses only CFFI. Both CFFI and
PyO3 already handle all the details of abstracting over the C API to enable
different build configurations and there is no need to laboriously port
extension types to use APIs that are only available on one build or another.

Using bindings generators will enable these projects to quickly adopt the new
stable ABI. Initial testing using the experimental ``_Py_OPAQUE_PYOBJECT`` flag
defined in CPython's ``main`` branch, indicates that PyO3, CFFI, and Cython will
all work with PEP 803 using packaging tools that have been patched to account
for an ``abi3.abi3t`` tag.

PyO3 maintainer `David Hewitt said
<https://github.com/python/peps/pull/4831#issuecomment-3972183807>`_ in
support of this proposal:

    PyO3 greatly benefits from having a stable ABI - one of the biggest
    challenges for the framework is the need to abstract over a wide range of
    Python / OS / CPU / environment combinations. We also offer the
    possibility to build with the stable ABI for each of these environments
    (targeting a given minimum Python version's stable ABI). The goal is
    always that all functionality PyO3 offers works the same on all these
    combinations (sometimes with a Python version floor to access certain
    features). We currently support Python 3.7+. All functionality added to
    the stable ABI is a very welcome promise that PyO3 will not need to
    introduce further conditional code to support a given feature. In the long
    run this makes it possible for PyO3 to simplify code paths as support for
    older Python versions is dropped, helping to keep maintenance burden under
    control.

When asked to comment about this proposal, Cython
maintainer `David Woods said
<https://github.com/cython/cython/issues/7399#issuecomment-3947605797>`_:

    Cython doesn't have huge problems with the number of wheels we distribute
    because ultimately it works fine as pure-Python. We do distribute wheels for
    a few of the smaller platforms as Stable ABI wheels but that's more
    "dogfooding" than because we actually need to. So I'm adding this in
    anticipation that other people will find it useful rather than because I
    will.

    I do remain slightly concerned that the performance trade-offs for this will
    turn out to be too much for many Cython users (it's possible that the
    trade-off may be different for other binding tools). That's not a huge
    disaster since we're not getting rid of the regular compilation mode so
    people are free to pick their own personal trade-offs.

It should be noted that this PEP leaves some questions/work open.
Wenzel Jakob -- maintainer of ``nanobind``, a C++ binding generator --
`noted <https://discuss.python.org/t/106181/6>`_
a need for additional API that is left out of scope of *this* PEP:

    It's not clear to me how I can get a pointer to the *N*-th data entry
    of [a ``PyVarObject``-derived object].

    If that can be resolved then nanobind should be able to adopt this new
    ``abi3t`` compilation target.


Rationale
=========

The design of ``abi3t`` involves several choices/assumptions/constraints:

Separate ABI
------------

The new ABI (``abi3t``) will conceptually be treated as separate from the
existing Stable ABI (``abi3``) – even though all extensions compatible with
``abi3t`` will, in practice, *also* be compatible with  ``abi3``.

(In more precise wording: ``abi3t``'s set of allowed APIs will be a
*subset* of ``abi3``'s; ``abi3t``'s set of compatible interpreters will
be a *superset* of ``abi3``'s. That makes one's head spin, which is part
of the reason to keep them separate.)

Extensions should be explicitly compiled for *both* ``abi3t`` and ``abi3``,
and should explicitly signal that they support both at once
-- via packaging wheel tags (``abi3.abi3t``) and a runtime ABI check
(:external+py3.15:c:macro:`Py_mod_abi`).

This explicitness has several advantages over having ``abi3t`` support
*imply* ``abi3`` support:

- The tags clearly show whether an extension is compatible with GIL-enabled
  builds, and whether the existing backwards compatibility guarantees of
  ``abi3`` apply.
- Implementation-wise, the set of tags a given free-threaded interpreter
  supports (as returned from the :pypi:`packaging` function
  ``packaging.tags.sys_tags``) It will be the same size as for a corresponding
  GIL-enabled build.
- It allows the ABIs to diverge slightly in the future -- keeping
  *both at once* as the preferred compilation target, but allowing
  ``abi3t``-only extensions for special cases.

One practical exception to keeping the ABIs conceptually separate is discussed
in the :ref:`803-filename-tag` section.

See these Rejected Ideas sections for more on the alternatives:

- :ref:`803-subset`
- :ref:`803-single`

No backwards compatibility now
------------------------------

CPython headers will not allow compiling for ``abi3t`` for CPython 3.14
and earlier.
Projects that need this can build separate extensions specifically
for the 3.14 free-threaded interpreter, and for older ``abi3``.

However, it *is* technically possible to build an extension compatible
with both free-threaded and GIL-enabled builds of CPython 3.14+.
To  enable experiments in this area, we recommend that package installation
tools are prepared for such extensions.
See a :ref:`rejected idea <pep803-no-shim>` for more details.

Source changes are necessary in extensions
------------------------------------------

``abi3t`` will require extension authors to make
significant changes to their code.

Projects that cannot do this (yet) can continue using ``abi3``,
and compile the same source for specific versions of free-threaded builds.
(Note that the APIs removed in ``abi3t`` still are usable when compiling for
a specific version, including 3.15t.)

See a Rejected Ideas sections for an alternative:
:ref:`803-freeze-pyobject`

Tag name
--------

The tag ``abi3t`` is chosen to reflect the fact that this ABI is similar to
``abi3``, with minimal changes necessary to support free-threading (which
uses the letter ``t`` in existing, version-specific ABI tags like ``cp314t``).

See a Rejected Ideas sections for an alternative:
:ref:`803-abi4`

.. _803-filename-tag:

Filename tag
------------

On systems that use the ``abi3`` tag in filenames, a new filename tag
(``abi3t``) is added so that older stable ABI extensions
(:samp:`{name}.abi3.so`) can be installed in the same directory as ones that
support Stable ABI for free-threaded Python (:samp:`{name}.abi3t.so`).

There can only be one ABI tag in a filename (there is no concept of "compressed
tag sets like in wheel tags), so extensions that are compatible with both ABIs
at once need to use *one* of the tags -- the new one (``abi3t``), as the
existing one has existing meaning.

See Rejected Ideas sections for alternatives:

- :ref:`803-combined-filename-tag`
- :ref:`803-bare-so`

Knob name
---------

This PEP specifies that the C preprocessor macro ``Py_TARGET_ABI3T``
will enable compiling for ``abi3t`` (that is, practically: it will make
``Python.h`` only expose forward-compatible definitions).

The corresponding "knob" for ``abi3`` is named ``Py_LIMITED_API``.
This name is problematic:

- It describes what the macro's historical internal effect (limiting which
  definitions are exposed), but not the intended benefit (forward
  compatibility).
- It is increasingly a misnomer: for API like ``Py_TYPE``, it selects
  a forward-compatible implementation (DLL function call rather than inline
  pointer deference) rather than limiting the API.
- The pair of terms *Stable ABI* and *Limited API* is technically accurate,
  but quite confusing.
  Avoiding the term *Limited API*, and talking about "constraints necessary
  for targeting a given ABI", tends to be clearer.

The proposed macro name (``Py_TARGET_ABI3T``) emphasizes ``abi3t`` as a
*compilation target*, with API limitations as its implicit price -- and forward
compatibility as the implicit benefit.

As for ``Py_LIMITED_API``, this PEP proposes no change, which means keeping
it for ``abi3``.
``abi3`` is expected to eventually become irrelevant *if* free-threaded builds
replace the GIL-enabled ones (see the
`PEP 703 acceptance notice <https://discuss.python.org/t/37075>`__
for the tentative plan).
At that point, ``Py_LIMITED_API`` will likely remain user-visible, but as an
implementation detail.

See a Rejected Ideas sections for an alternative -- reusing an existing "knob":
:ref:`803-knob-gildisabled`


Specification
=============


Stable ABI for free-threaded builds
-----------------------------------

Python will introduce a new stable ABI, called *stable ABI for free-threading
builds*, or ``abi3t`` for short.
As with the current Stable ABI (``abi3``), ``abi3t`` will be versioned
using major (3) and minor versions of Python interpreter.
Extensions built for ``abi3t`` :samp:`3.{x}` will be compatible with
free-threading builds of CPython :samp:`3.{x}` and above.

This mirrors the compatibility promise for the existing Stable ABI, ``abi3``,
which was defined :pep:`384#abstract` and modified in
:pep:`703#backwards-compatibility`:
Extensions built for ``abi3`` :samp:`3.{x}` will be compatible
with *GIL-enabled* builds of CPython :samp:`3.{x}` and above.

To build a C/C++ extension for ``abi3t``, the extension will need to limit
itself to only use API for which we can promise long-term support.
This *limited API for free-threaded builds* will be a subset of the
3.15 Limited API.

Any extension compiled for ``abi3t`` will, in practice, be compatible with
``abi3`` as well.
However, we recommend that users and tools explicitly compile for
*both at the same time*, and signal this explicitly.
(In the PyPA packaging ecosystem, this signaling means using the wheel tag
``abi3.abi3t`` as detailed below).


Choosing the target ABI
-----------------------

Users of the C API -- or build tools acting on their behalf, configured by
tool-specific UI -- will select the target ABI using the following macros:

* ``Py_LIMITED_API=<version>`` (existing):
  Compile for ``abi3`` of the given version.
* ``Py_TARGET_ABI3T=<version>`` (proposed here):
  Compile for ``abi3t`` of the given version.

For ease of use and implementation simplicity, respectively, ``Python.h`` will
set the configuration macros automatically in the following situations:

* If :samp:`Py_LIMITED_API={v}` and ``Py_GIL_DISABLED`` is set, then
  ``Py_TARGET_ABI3T`` will be defined as :samp:`{v}` by default.
  (This allows choosing ``abi3t`` by defining the pre-existing macro and
  compiling with free-threaded CPython headers. Note that in CPython 3.14,
  this case results in a compile-time error.)

* If :samp:`Py_TARGET_ABI3T={v}` is set, CPython *may* define or redefine
  ``Py_LIMITED_API`` as :samp:`{v}`.
  (This means that CPython can continue to use the ``Py_LIMITED_API`` macro
  internally to select which APIs are available.)

Also, if ``Py_TARGET_ABI3T`` is defined, then ``Python.h`` will make sure
that ``Py_GIL_DISABLED`` is defined as well.
Users may check this macro to enable free-threading-specific code like
extra locking.


Opaque PyObject
---------------

``abi3t`` will initially have a single difference from ``abi3``: the
``PyObject`` structure and APIs that depend on it are not part of ``abi3t``.

Specifically, when building for ``abi3t``, the CPython headers will:

- make the following structures *opaque* (or in C terminology, *incomplete
  types*):

  - :c:type:`PyObject`
  - :c:type:`PyVarObject`
  - :c:type:`!PyModuleDef_Base`
  - :c:type:`PyModuleDef`

- no longer include the following macros:

  - :c:macro:`PyObject_HEAD`
  - :c:macro:`!_PyObject_EXTRA_INIT`
  - :c:macro:`PyObject_HEAD_INIT`
  - :c:macro:`PyObject_VAR_HEAD`
  - :c:func:`Py_SET_TYPE`

In both the regular stable ABI (``abi3`` 3.15+) and the new
``abi3t``, the following will be exported functions (exposed in the ABI)
rather than macros:

- :c:func:`Py_SIZE`
- :c:func:`Py_SET_SIZE`
- :c:func:`Py_IS_TYPE`


Implications
^^^^^^^^^^^^

Making the ``PyObject``, ``PyVarObject`` and ``PyModuleDef`` structures
opaque means:

- Their fields may not be directly accessed.

  For example, instead of ``o->ob_type``, extensions must use
  ``Py_TYPE(o)``.
  This usage has been the preferred practice for some time.

- Their size and alignment will not be available.
  Expressions such as ``sizeof(PyObject)`` will no longer work.

- They cannot be embedded in other structures.
  This mainly affects instance structs of extension-defined types,
  which will need to be defined using API added in :pep:`697` -- that is,
  using a ``struct`` *without* ``PyObject`` (or other base class struct) at
  the beginning, with :c:func:`PyObject_GetTypeData` calls needed to access
  the memory.

- Variables of these types cannot be created.
  This mainly affects static ``PyModuleDef`` variables needed to define
  extension modules.
  Virtually all extensions will need to switch the new export hook
  added in :pep:`793` (:c:func:`PyModExport_modulename`) to support ``abi3t``.

The following functions will become practically unusable in ``abi3t``,
since an extension cannot create valid, statically allocated, input
for them.
They will, however, not be removed.

- :c:func:`PyModuleDef_Init`
- :c:func:`PyModule_Create`, :c:func:`PyModule_Create2`
- :c:func:`PyModule_FromDefAndSpec`, :c:func:`PyModule_FromDefAndSpec2`


.. _803-runtime-check:

Runtime ABI checks
------------------

Users -- or rather build/install tools acting on users' behalf --
will continue to be responsible for not putting incompatible extensions on
Python's import paths.
This decision makes sense since tools typically have much richer metadata than
what CPython can check.
Typically, build tools and installers use `PyPA packaging metadata`_ and
`platform compatibility tags`_ to communicate compatibility details, but other
models are possible.

.. _PyPA packaging metadata: https://packaging.python.org/en/latest/specifications/core-metadata/
.. _platform compatibility tags: https://packaging.python.org/en/latest/specifications/platform-compatibility-tags/

However, CPython will add a line of defense against outdated or misconfigured
tools, or human mistakes, in the form of a new *module slot*, ``Py_mod_abi``,
containing basic ABI information.
This information will be checked when a module is loaded, and incompatible
extensions will be rejected.
The specifics are left to the C API working group.
(See `capi-workgroup issue 72 <https://github.com/capi-workgroup/decisions/issues/72>`__, which was implemented well before this PEP was finalized.
Additionally, a ``PyABIInfo_FREETHREADING_AGNOSTIC``
flag for ``PyABIInfo.flags`` will be added to signal compatibility with both
``abi3`` and ``abi3t``.)

This slot will become *mandatory* with the new export hook added in
:pep:`793`.
(That PEP currently says “there are no required slots”; it will be updated.)


Check for non-free-threaded ABI in free-threading builds
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Additionally, in free-threaded builds, :c:func:`PyModuleDef_Init` will detect
extensions using the non-free-threading Stable ABI, emit an informative
message when one is loaded, *and* raise an exception.
(Implementation note: A message will be printed before raising the exception,
because extensions that attempt to handle an exception using incompatible ABI
will likely crash and lose the exception's message.)

This check for non-free-threading ``abi3`` relies on internal bit patterns
and may be removed in future CPython versions,
if the internal object layout needs to change.


The ``abi3t`` wheel and filename tags
-------------------------------------

Wheels with extension modules compiled for Stable ABI for Free-Threaded Python
should use a new `ABI tag`_: ``abi3t``.

.. _ABI tag: https://packaging.python.org/en/latest/specifications/platform-compatibility-tags/#abi-tag

On systems where filenames of Stable ABI extensions end with ``.abi3.so``,
extensions that support free-threading should instead use ``abi3t.so``.
This includes extensions compatible with *both* ``abi3`` and ``abi3t``.

On these systems, all builds of CPython -- GIL-enabled and free-threaded --
will load extensions with the ``abi3t`` tag.
Free-threaded builds will -- unlike in 3.14 -- *not* load extensions
with the ``abi3`` tag.
If files are present with both tags, GIL-enabled builds will prefer
"their" ``*.abi3.so`` over ``*.abi3t.so``.

Put another way, ``importlib.machinery.EXTENSION_SUFFIXES`` will be
(for ``x86_64-linux-gnu`` builds of CPython):

* ``python3.15``:
  ``['.cpython-315-x86_64-linux-gnu.so', '.abi3.so', '.abi3t.so', '.so']``
* ``python3.15t``:
  ``['.cpython-315-x86_64-linux-gnu.so', '.abi3t.so', '.so']``

Making GIL-enabled builds load ``.abi3t.so`` files is purely a practical
choice: it this one case we break the conceptual purity of ``abi3`` and
``abi3t`` being separate ABIs.


Recommendations for installers
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Package installers should allow ``abi3t``-tagged wheels for free-threaded
builds wherever they currently allow ``abi3``-tagged ones for (otherwise equal)
non-free-threaded builds.

Note that this PEP does not provide a way to target Stable ABI for
Free-threaded Python 3.14 (``cp314-abi3t``) and below.
This may change an the future, or with experimental build tools, so
installers should be prepared for such extensions.


Recommendations for build tools
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Build tools should give users a new option: in addition to compiling for
the existing CPython version-specific ABI (:samp:`cp3{nn}`) and
Stable ABI (``abi3``), they should allow
compiling extensions for *both* ``abi3`` and ``abi3t`` at once, by either:

- defining both :samp:`Py_LIMITED_API={v}` and :samp:`Py_TARGET_ABI3T={v}`, or
- defining :samp:`Py_LIMITED_API={v}` and:

  - defining ``Py_GIL_DISABLED`` (on Windows)
  - building with free-threaded CPython headers (elsewhere)

In the above, :samp:`{v}` stands for the lowest Python version with which
the extension should be compatible, in :c:func:`Py_PACK_VERSION` format.
This version must be 3.15 or higher.

On systems that use ABI version tagged ``.so`` files as introduced in
:pep:`3149` (Linux, macOS, and similar), the extension should be named
:samp:`{modulename}.abi3t.so`.
Otherwise, there should be no change: on Windows, use :samp:`{name}.pyd`.

Wheels containing such extensions should be tagged with the
`compressed ABI tag set <https://packaging.python.org/en/latest/specifications/platform-compatibility-tags/#compressed-tag-sets>`_
``abi3.abi3t``, and
the `Python tag`_ :samp:`cp{3yy}` corresponding to :samp:`{v}` above.

For example, a wheel tagged ``cp315-abi3.abi3t`` will be compatible with
3.15, 3.16, and later versions;
``cp317-abi3.abi3t`` will be compatible with 3.17+.

.. _Python tag: https://packaging.python.org/en/latest/specifications/platform-compatibility-tags/#python-tag

It is discouraged, but possible, to compile extensions compatible with *only*
``abi3t`` (by defining only :samp:`Py_TARGET_ABI3T={v}` and tagging the
resulting wheel with ``abi3t`` rather than ``abi3.abi3t``).
This will limit the result to free-threaded interpreters only.

Its is also possible to build ``abi3t`` extensions compatible with CPython 3.14
(or even lower versions), but this is unsupported and would require
detailed understanding of the limitations and guarantees,
along with thorough testing.


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 will be added via the
C API working group, or in a follow-up PEP.
(Note: :external+py3.15:c:type:`PyCriticalSection` API was added to 3.15 in
`C API WG issue 100 <https://github.com/capi-workgroup/decisions/issues/100>`__.)


Backwards and Forwards Compatibility
====================================

Extensions targeting ``abi3t`` will not be backwards-compatible with older
CPython releases, neither at the source level (API) nor in compiled form (ABI),
due to the need to avoid ``PyModuleDef`` and use new ``PyModExport`` hook added
in :pep:`793`.

Extension authors who cannot switch may continue to use the existing ``abi3``,
that is, build on GIL-enabled Python without defining ``Py_GIL_DISABLED``.
For compatibility with free-threaded builds, they can compile using
version-specific ABI -- that is, compile ``abi3``-compatible source
on free-threaded CPython builds without defining ``Py_TARGET_ABI3T``.

As for the existing Stable ABI: ``abi3`` :samp:`3.{x}` continues to be
compatible with *GIL-enabled* CPython :samp:`3.{x}` or above,
as promised in :pep:`384#abstract` and amended in
:pep:`703#backwards-compatibility`.


Compatibility Overview
----------------------

The following table summarizes compatibility of wheel tags with CPython
interpreters. “GIL” stands for GIL-enabled interpreter; “FT” stands for a
free-threaded one.

.. list-table::
   :widths: auto
   :header-rows: 1

   * * Wheel tag
     * 3.14 (GIL)
     * 3.14 (FT)
     * 3.15 (GIL)
     * 3.15 (FT)
     * 3.16+ (GIL)
     * 3.16+ (FT)
   * * ``cp314-cp314``
     * ✅
     * ❌
     * ❌
     * ❌
     * ❌
     * ❌
   * * ``cp314-cp314t``
     * ❌
     * ✅
     * ❌
     * ❌
     * ❌
     * ❌
   * * ``cp314-abi3``
     * ✅
     * ❌
     * ✅
     * ❌
     * ✅
     * ❌
   * * ``cp314-abi3t`` (*)
     * ❌
     * ✅
     * ❌
     * ✅
     * ❌
     * ✅
   * * ``cp314-abi3.abi3t`` (*)
     * ✅
     * ✅
     * ✅
     * ✅
     * ✅
     * ✅
   * * ``cp315-cp315``
     * ❌
     * ❌
     * ✅
     * ❌
     * ❌
     * ❌
   * * ``cp315-cp315t``
     * ❌
     * ❌
     * ❌
     * ✅
     * ❌
     * ❌
   * * ``cp315-abi3``
     * ❌
     * ❌
     * ✅
     * ❌
     * ✅
     * ❌
   * * ``cp315-abi3t``
     * ❌
     * ❌
     * ❌
     * ✅
     * ❌
     * ✅
   * * ``cp315-abi3.abi3t``
     * ❌
     * ❌
     * ✅
     * ✅
     * ✅
     * ✅

(*): Wheels with these tags cannot be built; see table below

The following table summarizes which wheel tag should be used for an extension
built with the given interpreter and defined macros:

+-----------------------+-------------+--------------------+---------------------+-----------+
| To get the wheel tag… | Compile on… | ``Py_LIMITED_API`` | ``Py_TARGET_ABI3T`` | Note      |
+=======================+=============+====================+=====================+===========+
| ``cp314-cp314``       | 3.14 (GIL)  | ---                | N/A                 | existing  |
+-----------------------+-------------+--------------------+---------------------+-----------+
| ``cp314-cp314t``      | 3.14 (FT)   | ---                | N/A                 | existing  |
+-----------------------+-------------+--------------------+---------------------+-----------+
| ``cp314-abi3``        | 3.14+ (GIL) | 3.14               | N/A                 | existing  |
+-----------------------+-------------+--------------------+---------------------+-----------+
| ``cp314-abi3t``       | N/A                                                    | reserved  |
+-----------------------+-------------+--------------------+---------------------+-----------+
| ``cp314-abi3.abi3t``  | N/A                                                    | reserved  |
+-----------------------+-------------+--------------------+---------------------+-----------+
| ``cp315-cp315``       | 3.15 (GIL)  | ---                | ---                 | continued |
+-----------------------+-------------+--------------------+---------------------+-----------+
| ``cp315-cp315t``      | 3.15 (FT)   | ---                | ---                 | continued |
+-----------------------+-------------+--------------------+---------------------+-----------+
| ``cp315-abi3``        | 3.15+ (GIL) | 3.15               | ---                 | continued |
+-----------------------+-------------+--------------------+---------------------+-----------+
| ``cp315-abi3t``       | 3.15+       | ---                | 3.15                | new       |
+-----------------------+-------------+--------------------+---------------------+-----------+
| ``cp315-abi3.abi3t``  | 3.15+ (FT)  | 3.15               | ---                 | new       |
+                       +-------------+--------------------+---------------------+           +
|                       | 3.15+       | 3.15               | 3.15                |           |
+-----------------------+-------------+--------------------+---------------------+-----------+


In the “Compile on” column, *FT* means that the :c:macro:`Py_GIL_DISABLED`
macro must be defined -- either explicitly or, on non-Windows platforms,
by including CPython headers configured with :option:`--disable-gil`.
*GIL* means that :c:macro:`Py_GIL_DISABLED` must *not* be defined.
Rows without this note apply to both cases.

In the ``Py_LIMITED_API`` and ``Py_TARGET_ABI3T``, a dash means the macro
must not be defined; a version means the macro must be set to the corresponding
integer in :c:func:`Py_PACK_VERSION` format.

Values in the *Note* column:

* *existing*: The wheel tag is currently in use
* *continued*: The wheel tag continues the existing scheme
* *new*: Proposed in this PEP.
* *reserved*: A mechanism to build a matching extension is not proposed in
  this PEP, but may be added in the future.
  Installers should be prepared to handle the tag.


Security Implications
=====================

None known.


How to Teach This
=================

A porting guide will need to explain how to move to APIs added in
:pep:`697` (Limited C API for Extending Opaque Types)
and :pep:`793` (``PyModExport``).


Reference Implementation
========================

This PEP combines several pieces, implemented individually:

- Opaque ``PyObject`` is available in CPython main branch after defining the
  ``_Py_OPAQUE_PYOBJECT`` macro.
  Implemented in GitHub pull request `python/cpython#136505
  <https://github.com/python/cpython/pull/136505>`__.
- For ``PyModExport``, see :pep:`793` and
  `GitHub issue #140550 <https://github.com/python/cpython/issues/140550>`_.
- A version-checking slot was implemented in GitHub pull request
  `python/cpython#137212 <https://github.com/python/cpython/pull/137212>`__.
- A check for older ``abi3`` was implemented in GitHub pull request
  `python/cpython#137957 <https://github.com/python/cpython/pull/137957>`__.
- The ``packaging`` project implemented wheel tag handling in installers in
  `pypa/packaging/pull#1099 <https://github.com/pypa/packaging/pull/1099>`__.
- For build tools, several individual pull requests were made;
  contact Nathan for details.

After this PEP was accepted, the implementation was tracked in
`CPython issue 146636 <https://github.com/python/cpython/issues/146636>`__.


.. _pep803-rejected-ideas:

Rejected Ideas
==============

.. _803-single:

Single new ABI: make ``PyObject`` opaque in Stable ABI 3.15
-----------------------------------------------------------

It would be possible to make ``PyObject`` struct opaque in Stable ABI
rather than introduce a new variant of the Stable ABI.

This would mean that extension authors would need to adapt their code to the
new limitations, or abandon Stable ABI altogether, in order to use any C API
introduced in Python 3.15.

It would also not fully remove the case for a new wheel tag (``abi3t``),
which would be required to express that an extension
is compatible with both GIL-enabled and free-threaded builds
of CPython 3.14 or lower.

In the `PEP discussion <https://discuss.python.org/t/pep-803-stable-abi-for-free-threaded-builds/103628/56>`__,
the ability to build for the GIL-only Stable ABI with no source changes
was deemed to be worth an extra configuration macro (now called
``Py_TARGET_ABI3T``).


.. _pep803-no-shim:

Shims for compatibility with CPython 3.14
-----------------------------------------

It’s possible to build a ``cp314-abi3.abi3t`` extension – one compatible
with 3.14 (both free-threaded build and default).
There are several challenges around this:

* making it convenient and safe for general extensions
* testing it (as CPython’s test suite doesn’t involve other CPython versions
  than the one being tested)

So, providing a mechanism to build such extensions is best suited to an
external project (for example, one like `pythoncapi-compat`_).
It's out of scope for CPython’s C API, and this PEP.

.. _pythoncapi-compat: https://github.com/python/pythoncapi-compat

To sketch how such a mechanism could work:

The main issue that prevents compatibility with Python 3.14 is that with
opaque ``PyObject`` and ``PyModuleDef``, it is not feasible to initialize
an extension module.
The solution, :pep:`793`, is only being added in Python 3.15.

It is possible to work around this using the fact that the 3.14 ABIs (both
free-threading and GIL-enabled) are “frozen”, so it is possible for an
extension to query the running interpreter, and for 3.14, use
a ``struct`` definition corresponding to the detected build's ``PyModuleDef``.


.. _803-subset:

Making ``abi3t`` compatible with ``abi3``
-----------------------------------------

It would be possible to teach packaging tools that ``abi3t`` is a "subset"
of ``abi3``, that is, all GIL-enabled interpreters are guaranteed to be
compatible with ``abi3t``-tagged builds.
This would make the ``abi3.abi3t`` compressed tag set equivalent to
``abi3t``, and thus redundant.
However, tools would still need to output the compressed tag set to support
"older installers", which do *not* consider ``abi3t`` compatible with
GIL-enabled builds.

Here, "older installers" include ones that use or vendor an version of the
:pypi:`packaging` library that wasn't updated for ``abi3t``.
(The ``packaging`` library is what Python-based installers typically use
to implement wheel tag matching.)

Beyond installers, the ``abi3.abi3t`` tag allow mechanisms like the ABI filter
in PyPI file list (e.g. on `<https://pypi.org/project/cryptography/#files>`__)
to match ``abi3`` without special-casing (assuming compressed tags are handled
according to standard).
Humans wondering about compatibility with ``abi3`` also get a more
explicit signal.

Less importantly, merging the ABIs would also remove an "escape hatch" of
possibly making ``abi3`` and ``abi3t`` diverge in the future.


.. _803-abi4:

Naming this ``abi4``
--------------------

Instead of ``abi3t``, we could “bump the version” and use ``abi4`` instead.
The difference is largely cosmetic.

If we added an ``abi4`` tag, the value of the opt-in macro (``Py_TARGET_ABI4``
or ``Py_LIMITED_API`` or some such)  would either need to:

* change to start with ``4`` to match ``abi4``, but no longer correspond
  to ``PY_VERSION_HEX`` (making it harder to generate and check), or
* not change, making it inconsistent with ``abi4``.

Adding ``abi3t`` is a smaller change than adding ``abi4``, making it work
better as a transitional state before larger changes like :pep:`809`'s
``abi2026``.


.. _803-combined-filename-tag:

``abi3+abi3t`` filename tag
---------------------------

Filename ABI tags (as introduced in :pep:`3149`) allow extensions for several
ABIs to co-exist in a directory.

Per this PEP, extensions that are compatible with both ``abi3`` and ``abi3t``
will use a compressed tag set (``abi3.abi3t``) in wheel metadata,
but not in filenames (``.abi3.so``/``.abi3t.so``).
We *could* add a dedicated tag for the combination -- for example,
``.abi3+abi3t.so``.

But, there would be no need for ``.abi3+abi3t.so`` extensions to co-exist with
``.abi3t.so`` ones: free-threaded interpreters would always pick ``.abi3t.so``,
so the extension for GIL-enabled interpreters could just as well use
``.abi3.so``.
The only benefit would be clearer naming when an ``abi3.abi3t`` extension
is *not* installed together with its ``abi3``-only equivalent.

Here, clearer naming is not worth the complexity.
We make the practical choice to make ``.abi3t.so`` mean
"abi3+abi3t", that is, "loadable by all builds".
This works for (discouraged) ``abi3t``-only extensions: on a GIL-enabled
interpreter, these will fail the mandatory :ref:`runtime ABI check <803-runtime-check>`
or, in the unlikely future where ``abi3t`` & ``abi3`` diverge, possibly fail
to load due to a missing linker symbol.

Conceptually, filename tags do not "describe" or "name" an extension's ABI.
The current ``.abi3`` tag is already too weak for this, as it lacks a version.


.. _803-bare-so:

No filename tag (bare ``.so``)
------------------------------

It would be possible to drop the filename ABI tag altogether,
and use ``.so`` instead of ``.abi3t.so``.
The practical meaning of these two tags is very close:
``.so`` is loadable by *any* build of CPython;
``.abi3t.so`` will be loadable by any CPython *3.15 or above* -- but the
Stable ABI filename tag already lacks version information.

They are different semantically, though.
Bare ``.so`` means "don't care"; an ``.abi3t.so`` extension is *intentionally*
compatible with the new ABI.


.. _803-knob-gildisabled:

Reusing ``Py_GIL_DISABLED`` to enable the new ABI
-------------------------------------------------

It would be possible to select ``abi3t`` (rather than ``abi3``) when the
``Py_GIL_DISABLED`` macro is defined together with ``Py_LIMITED_API``.

This would require annoying fiddling with build flags, and make it
impossible to explicitly target both ``abi3`` and ``abi3t`` at the same time.

.. _803-freeze-pyobject:

Fully separate ABIs, keeping source compatibility
-------------------------------------------------

It would be possible to make ``abi3`` and ``abi3t`` fully separate and
incompatible.
This would allow all current extensions to stay *source*-compatible with
``abi3t``: the ``PyObject`` struct could stay exposed, with each ABI
defining a different set of private fields, as in the version-specific
CPython ABI.

However, exposed ``PyObject`` struct has been `noted <https://github.com/capi-workgroup/problems/issues/4>`__
as one of the main shortcomings of the existing Stable ABI.
It hindered or prevented optimizations and features such as immortalization
and free-threading itself.

Exposing ``PyObject`` would mean repeating this mistake, "freezing"
its current free-threaded definition, and requiring
*yet another* variant of stable ABI if/when changes are needed.


Copyright
=========

This document is placed in the public domain or under the
CC0-1.0-Universal license, whichever is more permissive.
