I spent yesterday and this morning implementing a version of @dstufft’s proposal in a set of packaging tools. To explore the dual compatibility story a bit better, I made it so abi3t wheels are loadable on the GIL-enabled build and there is no abi3.abi3t or any kind of combined build that ships both extensions. There are only abi3 wheels that contain extensions with .abi3.so suffixes that only work on the GIL-enabled build, or abi3t wheels that contain extensions with .abi3t.so suffixes that work on both builds.
My goal is to check whether this maximalist version of the proposal that people seem to be worried about will actually cause issues in real-world projects.
My fork of setuptools currently requires a free-threaded interpreter to generate an abi3t wheel. It’s possible to add UI for this as well, although projects may need to manually define Py_GIL_DISABLED for such builds, setuptools doesn’t set any compiler defines.
All of this is based on the private _Py_OPAQUE_PYOBJECT macro, which will presumably be replaced by Py_TARGET_ABI3T or similar if PEP 803 is accepted.
tl;dr
I tested a variety of build tools and real-world projects that plan to or currently ship abi3 wheels. The only significant issue I had in any project was with astropy, since it ships static types and static module definitions that will need to be updated, so it cannot easily support the new limited API right now.
Other than that, I have not seen any crashes or deadlocks on either build using an .abi3t.so file extension and a cp315-abi3t wheel tag with no compressed ABI tag sets.
CPython patch
I have a very small patch for CPython that updates importlib.machinery.EXTENSION_SUFFIXES and adds a test as well:
The way I set it up, the GIL-enabled build loads both extensions with .abi3.so and .abi3t.so suffixes. I haven’t yet stress-tested scenarios where both builds are available in the same package, but I think the sensible thing is to prefer .abi3.so if both are found. I think that’s actually how it works in my branch, I just haven’t added any integration tests to prove that. I also found that Meson is senstive to the ordering of this list, see below.
Stable ABI Test Harness
If you build CPython from my branch and want to experiment with builds using the abi3t ABI, you’ll also need to do something analogous to this series of pip install commands to install various forks of projects that need patching to support opaque PyObject builds:
NO_CYTHON_COMPILE=true python3.15t -m pip install git+https://github.com/cython/cython.git@freethreading-limited-api-preview
python3.15t -m pip install git+https://github.com/ngoldbaum/pip.git@abi3t
python3.15t -m pip install git+https://github.com/ngoldbaum/packaging.git@abi3t
python3.15t -m pip install git+https://github.com/ngoldbaum/setuptools.git@abi3t
python3.15t -m pip install git+https://github.com/ngoldbaum/meson-python.git@abi3t
python3.15t -m pip install git+https://github.com/ngoldbaum/cffi.git@abi3t
python3.15t -m pip install git+https://github.com/kumaraditya303/numpy.git@opaque --no-build-isolation
The diffs for the packaging tools are all pretty small. If anything, this approach without the compressed ABI tag set is simpler, because there’s no need to teach build backends about the existence of compressed ABI tag sets.
That said, while typing this post I noticed that Meson will also maybe need to be patched to produce .abi3t.so extensions on the GIL-enabled build, since it uses a static index into EXTENSION_SUFFIXES to find the correct .so file name for limited API builds. If we made the GIL-enabled interpreter prefer .abi3t.so extensions the current code would also work correctly, but I don’t think we want that.
@mgorny and I have been working on a set of tests for packaging tools in this repo: GitHub - Quansight-Labs/stable-abi-testing: Projects to test the upcoming the Python stable ABI changes · GitHub . It builds extensions with a matrix of build tools, programming languages, and binding generators, with a goal of sniffing out incompatibility with PEP 803. The extensions are very simple, they just expose an add function that adds two C ints. It verifies that the extension builds correctly, with the correct ABI tag in the wheel file, and verifies that the extension can be successfully imported and executed on both builds, if you happen to have both builds available in your PATH.
Currently the stable ABI tests target the version of PEP 803 from before @dstufft pointed out the issues with Debian and shared installs, so I have a branch that updates everything:
All the tests pass on my Mac dev machine with CPython builds from my branch. You can run the build with tox -e py. You can run it with either build or with both on your PATH.
Note that the only “real” tests are the ones using meson-python and setuptools. We still need to update scikit-build-core and maturin. Support in PyO3 will also need to wait until PyCriticalSection and related API land in the stable ABI.
Testing with real-world packages.
I also spent some time this morning testing out @steve.dower’s worries about using abi3t extensions on the GIL enabled build. Are the code blocks in Py_GIL_DISABLED blocks in extensions problematic on the GIL-enabled build?
With the limited testing I did today, I didn’t find any issues so far.
Pywavelets
Pywavelets currently ships cp314t wheels and has been worked on already to support the free-threaded build. I was able to successfully build a cp315-abi3t wheel based on @rgommers’ branch. The pywavelets tests all pass on both builds. I also tried testing with pytest-run-parallel and did not see any deadlocks or other issues. All tests that pytest-run-parallel does not automatically mark as thread-unsafe pass in parallel. Pywavelets doesn’t have any explicitly multithreaded stress tests, so this is all testing for global state in the Cython-generated code as well as trying to assess if there are any mutexes or other possibly problematic types in Py_GIL_DISABLED blocks generated by Cython. That said, pywavelets also doesn’t define any mutable types in its extensions, so it wouldn’t be obvious what to test.
cymem
cymem is a small Cython utility library that provides a memory pool type via a Cython .pxd header file. Our team worked on it earlier this year. It’s at the bottom of the dependency stack for spaCy. It’s a nice small example that also has nontrivial code to handle thread safety on the free-threaded build. In particular, it has a few critical sections.
@lys.nikolaou wrote a multithreaded test for cymem to validate thread safety. When I buidl an abi3t wheel on the free-threaded build, the test passes on the GIL-enabled build when I install the wheel.
brotlicffi
Brotlicffi recently started shipping cp314t wheels. There is a multithreaded test that checks to make sure concurrent use leads to errors (libbrotli is not thread-safe). I needed to make a very small patch to enable limited API builds. Because the brotlicffi build uses my fork of setuptools, you need a free-threaded interpreter to build an abi3t wheel. Allowing abi3t builds on the GIL-enabled interpreter will require some new UI in setuptools and CFFI, I think.
As far as I can tell, everything works on both builds when I install from the same abi3t wheel and run the brotlicffi tests.
yt
The yt project is an analysis and visualization tool. Once upon a time it was my day job to help maintain it. I learned this week that yt ships limited API wheels. To my surprise, everything seems to work. Note that no one has done any work to validate thread safety in yt and it does not yet support the free-threaded build. That means when I tried running the yt tests under pytest-run-paralell, I saw some thread safety issues due to global state in yt’s implementation. I didn’t see any deadlocks or crashes on either build.
This also required updating ewah-bool-utils, which yt depends on, to build abi3t wheels. I didn’t see any issues in that library either.
astropy
astropy also ships abi3 wheels. However, astropy does not rely entirely on Cython for C extensions, it also has some pure C extensions that define static types and use PyModuleDef_HEAD_INIT to initialize a few modules. I commented on the issue an astropy developer already opened about Python 3.15 stable ABI support. In the process, I also found extension-helpers, which astropy uses to help manage Cython extension builds. That project will also need a small patch to work with the new stable ABI.