Repository navigation
feat: support the Python stable ABI (abi3 and abi3t) - #6198
Conversation
|
If this is better in smaller parts, there are some good candidates for that, including two bug fixes and a nice performance speedup: 🤖 AI text below 🤖 Yes. Beyond the method-descriptor patch, there are two real bug fixes and one large feature that do not depend on Bug fixes (strongest candidates)
Feature, independent of the stable ABI
Method descriptor / instancemethod (the one you named)
Prep refactors, low value alone but they shrink the main diff
Not separable |
|
@henryiii this is the first pass reviewing with codex GPT-6.1-Sol ultra: Rechecked against 75be228. All six findings remain after the rebase:
Finding 2 was reproduced through compilation at this revision. The others are based on code inspection; no test suites were run. |
|
I'm going to revisit this soon, I don't agree with all the review fixes I applied. "Pin registry candidates before acquiring the internals lock" specifically is wrong, we should not be returning an object instead of a handle from |
b4aeb87 to
8f0a6d8
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved critical issues can cause use-after-free during free-threaded override lookup and deadlocks during concurrent initialization of Python-importing caches.
8 open issues
Avoid GIL and static-guard deadlock during chrono initialization · New Avoid GIL and static-guard deadlock importing types · New Return and retain an owning wrapper object · New Bind complex descriptors before calling · New Encode the limited-API version in guard symbols · New Read structural type attributes through original descriptors · New Derive _abi3t from final ABI macros · New Set importable module naming independently of ABI tags · New
What changed in this PR
Adds CPython stable-ABI support to pybind11, allowing downstream extensions to reuse binaries across Python versions, including free-threaded builds.
Changes:
- Adds abi3 and abi3t build options, naming, and compatibility checks.
- Adapts type creation, object access, conversions, and internal state for the limited API.
- Documents supported configurations and expands regression and cross-version CI coverage.
| File | Description |
|---|---|
| tools/pybind11Tools.cmake | Rejects stable ABI in classic discovery mode. |
| tools/pybind11NewTools.cmake | Configures stable-ABI targets, linkage, and suffixes. |
| tools/pybind11Common.cmake | Supports stable-ABI precompiled libraries. |
| tools/check_inl_headers.py | Recognizes new ABI configuration macros. |
| tests/test_thread.py | Skips unavailable native-thread lock tests. |
| tests/test_thread.cpp | Guards native-thread lock fixtures. |
| tests/test_pytypes.cpp | Adapts iterator tests for limited API. |
| tests/test_multiple_interpreters.py | Skips unsupported subinterpreter tests. |
| tests/test_multiple_inheritance.py | Tests multiple dynamic-attribute bases. |
| tests/test_multiple_inheritance.cpp | Adds dynamic-attribute inheritance fixtures. |
| tests/test_modules.cpp | Avoids opaque module definitions under abi3t. |
| tests/test_methods_and_attributes.py | Tests method descriptors and metaclass behavior. |
| tests/test_methods_and_attributes.cpp | Adds metaclass-conflict fixtures. |
| tests/test_kwargs_and_defaults.cpp | Uses public tuple accessors. |
| tests/test_custom_type_setup.py | Skips unavailable custom-type fixtures. |
| tests/test_cross_module_rtti/CMakeLists.txt | Keeps embedding bindings outside stable ABI. |
| tests/test_cmake_build/test.py | Checks expected stable-ABI filenames. |
| tests/test_cmake_build/subdirectory_stable_abi/CMakeLists.txt | Adds subdirectory stable-ABI integration tests. |
| tests/test_cmake_build/CMakeLists.txt | Registers and configures ABI build tests. |
| tests/test_class_release_gil_before_calling_cpp_dtor.py | Skips unavailable GIL-check fixtures. |
| tests/test_chrono.py | Tests subclass fields and translation-unit initialization. |
| tests/test_chrono_second_tu.cpp | Adds a second chrono translation unit. |
| tests/test_call_policies.py | Conditions GIL tests on fixture availability. |
| tests/test_call_policies.cpp | Guards unsupported GIL-state checks. |
| tests/test_builtin_casters.py | Tests complex-conversion edge cases. |
| tests/pybind11_tests.cpp | Exposes compile-time Python and ABI metadata. |
| tests/extra_setuptools/test_setuphelper.py | Tests stable-ABI builds and version selection. |
| tests/exo_planet_c_api.cpp | Adds the abi3t module export hook. |
| tests/env.py | Exposes limited-API test configuration. |
| tests/cross_module_interleaved_error_already_set.cpp | Supports abi3t module initialization. |
| tests/cross_module_gil_utils.cpp | Supports abi3t GIL-test module exports. |
| tests/conftest.py | Normalizes annotations using the build Python version. |
| tests/CMakeLists.txt | Configures stable-ABI test selection. |
| pybind11/setup_helpers.py | Adds ABI selection, filenames, and export handling. |
| include/pybind11/trampoline_self_life_support.h | Uses opaque-safe instance access. |
| include/pybind11/subinterpreter.h | Uses public interpreter-state accessors. |
| include/pybind11/stl.h | Adapts iterable checks and list access. |
| include/pybind11/pytypes.h | Adds limited-API object and type helpers. |
| include/pybind11/pytypes-inl.h | Removes direct traceback and code-field access. |
| include/pybind11/pybind11.h | Adds abi3t exports and opaque-safe operations. |
| include/pybind11/pybind11-inl.h | Adapts dispatch, overrides, and module initialization. |
| include/pybind11/numpy.h | Uses NumPy’s abi3t data accessors. |
| include/pybind11/gil_simple.h | Guards unavailable GIL assertions. |
| include/pybind11/functional.h | Uses a compatible function-self accessor. |
| include/pybind11/eval.h | Adds limited-API evaluation paths. |
| include/pybind11/embed.h | Rejects unsupported stable-ABI embedding. |
| include/pybind11/detail/value_and_holder.h | Adds opaque-safe holder access. |
| include/pybind11/detail/typeid.h | Handles incomplete PyObject types. |
| include/pybind11/detail/type_caster_base.h | Adapts instance and holder conversions. |
| include/pybind11/detail/type_caster_base-inl.h | Adds weak-reference-based abi3t instance lookup. |
| include/pybind11/detail/internals.h | Adds ABI isolation, opaque storage, and locking. |
| include/pybind11/detail/internals-inl.h | Uses critical sections for registry counting. |
| include/pybind11/detail/init.h | Adapts constructor and pickle instance access. |
| include/pybind11/detail/function_record_pyobject.h | Supports opaque function-record storage. |
| include/pybind11/detail/cpp_conduit.h | Removes an obsolete forward declaration. |
| include/pybind11/detail/cpp_conduit-inl.h | Uses registry and descriptor lookup helpers. |
| include/pybind11/detail/common.h | Defines ABI gates, exports, and instance-layout helpers. |
| include/pybind11/detail/class.h | Declares stable-ABI type and method helpers. |
| include/pybind11/detail/class-inl.h | Implements spec-based types and method descriptors. |
| include/pybind11/complex.h | Adds limited-API complex conversion. |
| include/pybind11/chrono.h | Adds descriptor-based datetime conversion. |
| include/pybind11/cast.h | Adapts casts, tuple writes, and GIL checks. |
| include/pybind11/attr.h | Guards unsupported custom type setup. |
| include/pybind11/attr-inl.h | Detects inherited dynamic attributes without struct access. |
| docs/index.rst | Links the stable-ABI guide. |
| docs/compiling.rst | Documents build options and precompile constraints. |
| docs/advanced/stable_abi.rst | Explains setup, limitations, and ABI isolation. |
| CMakeLists.txt | Exposes the type-creation mode override. |
| .github/workflows/ci.yml | Adds stable-ABI, cross-version, and legacy-path coverage. |
Review effort: Balanced
|
codex GPT-6 on behalf of Ralf: I prepared ten suggested-fix commits atop 1ea0ea0, one per issue, each with regression coverage. The clean series is in #6225, an open DO NOT MERGE PR used for CI validation.
Full CI is green on both initial validation #6224 and clean series #6225: each has 97 passing checks, two expected skips, and all three external status contexts passing. The clean series is tree-identical to the initial validation PR #6224, with CI follow-ups folded into the corresponding commits. Local full default/free-threaded and abi3 suites passed, as did C++11 precompiled checks and the same abi3t binary on GIL-enabled and free-threaded Python 3.15. Independent review found no remaining significant findings. These commits are offered for adoption or adaptation in the listed order. Neither validation PR is intended for merging. |
So I'll adopt 4-10, with some modifications. |
|
🤖 AI text below 🤖 Cherry-picked unchanged (5 to 8) with Ralf's authorship. I left the Rewritten (4, 9, 10), each noting the source commit in the message:
|
|
🤖 AI text below 🤖 Downstream testingI built 12 downstream projects against this branch (macOS arm64) in three ways:
I also ran
"No regressions" means some tests failed, but the same tests also failed with released pybind11. The causes were outside pybind11: Hypothesis, solver versions, and LaTeX or Tk in the environment. Some test runs had one or two extra limits:
Other checks:
What I changed on this branch:
I also found a problem that this PR did not cause: |
Py_LIMITED_API is not supported yet; a build with it defined failed with pages of unrelated errors. Add a single #error in common.h, and reserve a bit for Py_LIMITED_API in the precompiled configuration check so later stable-ABI work can switch it on. See pybind#6104 (B1). Assisted-by: ClaudeCode:claude-fable-5-1
…ULE_LOCAL_ID Modules built with Py_LIMITED_API will use a different internals layout, so they must not share internals with regular modules in one process. The tag is empty today and becomes "_stable" under Py_LIMITED_API. It is not part of PYBIND11_PLATFORM_ABI_ID, so cpp_conduit keeps bridging both worlds. See pybind#6104 (B2). Assisted-by: ClaudeCode:claude-fable-5-1
…p_bases/tp_mro Direct reads of tp_bases/tp_mro are not possible under the Python stable ABI. Route all reads through two small helpers so the limited-API branch can be added in one place later. See pybind#6104 (B3). Assisted-by: ClaudeCode:claude-fable-5-1
…tead of raw tp_name reads Direct reads of tp_name are not possible under the Python stable ABI. obj_class_name() now returns std::string. See pybind#6104 (B4). Assisted-by: ClaudeCode:claude-fable-5-1
_PyType_Lookup is a private CPython function and is not available under the stable ABI. Route the three users (metaclass getattro/setattro and the cpp_conduit instance-method check) through one helper. See pybind#6104 (B7). Assisted-by: ClaudeCode:claude-fable-5-1
… fallbacks Gate the PyPy fallbacks that exist because cpyext does not expose CPython struct layouts (static-property __set__, bool conversion, tuple/list fast iterators) on one semantic macro. The stable ABI needs the same fallbacks. See pybind#6104 (B9). Assisted-by: ClaudeCode:claude-fable-5-1
Use the function forms (available under the stable ABI) at the non-hot sites, and route the dispatcher and tuple/list caster sites through small detail:: wrappers that keep the macro forms when struct access is available. See pybind#6104 (B10). Assisted-by: ClaudeCode:claude-fable-5-1
|
To close the loop here, while reading
above it crossed my mind to ask about the performance impact of #6231. Codex jumped straight to a basic benchmark experiment. The results are shown here: |
|
— codex GPT-6 on behalf of Ralf — One scope observation, separate from the type-creation switch already landed in #6231: the remaining changes in this PR also affect some ordinary CPython builds, even with the Stable ABI disabled. Many existing fast paths are retained, but there are shared implementation changes. For example:
This is a source-level observation; I haven’t benchmarked these changes. If users report noticeable slowdowns, we can optimize the affected paths in follow-up PRs. |
It's a limited subset of CPython's API (Limited API). If you use that, you can build a ABI that CPython commits to keeping in future versions (Stable ABI). Practical (massive) benefit: No longer have to build wheels for every version of Python. Can reduce a massive build matrix into a small one. Less storage on PyPI (which projects often have to request size increases for!).
It is more abstracted from the implementation so it can be stable across future implementations. Most applications don't spend a lot of time in the binding layer, so this is fine most of the time; I'd recommend projects measure. I didn't see any measurable regressions in the projects I tested (at least after a couple of rounds of optimizations on this PR). And it's even not always negative, as in this PR we take a different, not quite backward compatible path to get better performance in one case with stable ABI than without. :) The other two comments were probably better answered by the AI. |
|
Here's a Fable 5.2 review: 🤖 AI text below 🤖 The review found 10 issues in Behavior changes and bugs
Lower risk
Efficiency and cleanup
Local |
- def_buffer: read tp_as_buffer directly in regular builds. PyType_GetSlot() does not see an inherited slot on Python 3.9, so a derived class without py::buffer_protocol() failed at import. - Type names: a Python subclass of a bound class keeps its bare name after all_type_info() caches it. - error_already_set: fail with a C++ exception if the exception type has no name; a null tp_name was undefined behavior. - get_interpreter_state_unchecked() uses PyInterpreterState_Get() under the stable ABI; get_thread_state_unchecked() is not available there. - get_staticmethod_type() imports builtins through gil_safe_call_once_and_store, like get_bound_method_type(). - type_lookup() does not raise KeyError for each miss below 3.13. - complex caster: keep the interned "__complex__" name in a static. - CMake: stable ABI modules and the stable ABI precompiled library do not link Python::Module (the version-specific library on Windows). - Remove three declarations without a definition; correct the tp_name comment in set_published_type_names(). Assisted-by: ClaudeCode:claude-fable-5-2
|
Applied fixes:
|
|
Chrono.h change summary: 🤖 AI text below 🤖 The The helper layerThe casters now call only these helpers:
The caster bodies ( Limited API details
Full API detail:
|
The limited-API path now uses PyInterpreterState_Get(), which requires an attached thread state and terminates if none exists. The "unchecked" suffix no longer describes that implementation. Rename the helper to get_current_interpreter_state() to describe what both API paths retrieve, and update all callers. Document the limited-API attachment requirement and the nullable result in ordinary builds. Attachment is the relevant condition for abi3t, where the GIL may be disabled. The lookup behavior is unchanged.
Since CPython 3.10, PyType_GetSlot() follows tp_as_buffer and correctly reports inherited buffer slots. Use it in ordinary Python 3.10+ builds as well as stable-ABI builds, keeping the direct-field workaround for 3.9. This also lets the existing buffer tests exercise the public-API lookup in ordinary builds. The lookup runs only during binding registration, so its performance cost is confined to setup. Individual buffer accesses incur no additional overhead. Retain defined(Py_LIMITED_API) in the condition even though the stable-ABI minimum of Python 3.12 makes it redundant. Document that it expresses the requirement to use the public slot API when type objects are opaque.
Python allows exception classes with empty names. Treating the empty string returned by the new name helper like the old null tp_name case therefore turns a valid Python exception into an unrelated RuntimeError. This is also reachable while formatting a secondary exception raised by __str__. Throwing from that path can escape error_already_set::what(), which is noexcept, and terminate the process instead of producing a diagnostic.
Keep this troubleshooting safeguard visible to both code readers and agents, and require review concerns to be raised when changes weaken it.
registered_types_py contains entries for both directly bound types and Python subclasses whose base-type information has been cached. The old name could be read as a membership check, but this predicate recognizes only the bound type itself. Add "directly" to make that distinction explicit alongside the broader type_is_managed_by_our_internals() predicate. The distinction matters to stable-ABI type-name formatting: caching a Python subclass must not change its reported name.
Removing pybind11::module from stable-ABI targets avoids linking the version-specific Windows Python library, but also bypasses the Android libpython workaround attached to Python::Module. With CMake 3.28, this leaves STABLE_ABI modules with unresolved Python symbols at link time. Keep Android's required library dependency available through Python::SABIModule as well. This also covers stable-ABI precompiled builds without bringing Python::Module back into their link graph. The regression test exercises Android link setup on Linux and uses --no-undefined to expose the missing dependency without an Android toolchain.
Preserve the primary-error review guidance alongside master's updated contribution and AI policies.
GitHub Actions left every workflow for 8206fd7 queued without creating jobs for about 30 minutes. Cancellation and force-cancellation reported that the run had not been queued, while rerunning said it was already running. Pre-commit.ci, ReadTheDocs, and AppVeyor passed. Trigger a fresh PR synchronization and check suite without changing the tree, to distinguish a stuck suite from a continuing dispatch problem.
The earlier retrigger, 783bf60, was pushed at 20:50 UTC during GitHub's Actions incident (20:48 to 21:21 UTC). Its suite still has no jobs, and force-cancellation remains unavailable after recovery. Trigger one fresh PR synchronization after GitHub confirmed full recovery and that its Actions backlog was processed. This tests whether a new suite can dispatch without the orphaned records. The tree is unchanged. Incident: https://stspg.io/jdjlq5zjyl48
|
Result of giving the codex GPT-6.1-Sol ultra:
I’d retain the support: the subclass semantics are preserved and initialization reuses established machinery. |
|
Result of giving the codex GPT-6.1-Sol ultra: Yes. The key distinction is existing knowledge of NumPy’s layouts versus the new mechanism for finding those layouts when Python’s object header is opaque.
The parallel with chrono is preserving stored-value semantics while bypassing subclass overrides. Here, NumPy-specific layout knowledge remains, while NumPy itself supplies the runtime-correct starting address. The implementation looks consistent with those goals. |
|
This is preliminary: tracking here, but only as expandable details: A close look at: Internals and Compatibility BoundariesI agree: The audit did uncover a separate function-record compatibility issue worth addressing. That needs a different fix, described below.
|
Make the existing implementation dependencies explicit so future CPython compatibility reviews can revisit them without inferring stronger guarantees from the use of Stable ABI functions.
|
Regarding point 5 in my previous comment: I just pushed commit dabeb4a. Sharing a follow-on prompt/response: My promptI want to look slightly beyond the horizon: is there a indication why the size/offset calculation was left as an implementation detail? Where my mind is going: C++ std::complex left .real and .imag struct layout as an implementation detail. Do we have a situation like that with the size/offset calculation, or should we be more worried? codex GPT-6.1-Sol ultraYes—there is a concrete design reason, and it makes me more cautious about expecting this convention to last another 25 years. In the original PEP discussion, Mark Shannon argued that the API should describe how to obtain a class’s data without prescribing its location. Petr Viktorin explicitly agreed: the function bodies illustrated current CPython, while the API should accommodate a future object-layout redesign. Mark’s critique, Petr’s response. The motivating example includes fixed-size classes with multiple inheritance. If two extension classes each contribute a C struct, Python could choose where those structs belong in a combined subclass. A base class’s data would then potentially have different offsets in different derived classes. Petr confirmed that the API should permit Python to make that placement decision. This concern extends beyond variable-sized objects such as tuples. Multiple-inheritance example and response. Our calculation nevertheless has substantial practical robustness: offset = base_basicsize - base_type_data_size;It calculates the offset at runtime, so a different object-header size or alignment does not, by itself, break it. Its consequential assumptions are:
There is an actual proposal that challenges those assumptions. The Grand Unified Python Object Layout proposal allows some extension structs before the core object and explicitly allows a base’s struct to move within derived objects. Whether such a redesign would affect existing PEP 697 types remains unsettled. There is also some recent interest: in April 2026, Petr expressed an intention to prepare a PEP for Python 3.16. I found no published PEP or accepted implementation in the primary sources checked. That comment establishes interest, rather than a release commitment. My engineering judgment: retaining the implementation remains a defensible practical choice, but I would justify it as a deliberate dependency on current CPython behavior, with a concrete upstream design direction to watch. I would place less confidence in its lasting for decades than your The stable-ABI context adds weight: if a future Python breaks this assumption, updating pybind11 would not repair already distributed wheels compiled with the old calculation; those wheels would need rebuilding. That makes the dependency relevant to the forward-compatibility promise, even though we have no demonstrated failure today. One interesting wrinkle in your analogy: today’s C++ specification explicitly guarantees the real/imaginary array-style access you describe. Your practical choice ultimately acquired a formal guarantee. Current C++ draft. |
— codex GPT-6 on behalf of Ralf — A std::function argument crossing from a full-API module to an abi3t module can trigger PyObject_GetTypeData() on a type created with positive basicsize. The shared wrapper name makes the incompatible object pass recognition; the reverse direction also assumes a different layout. Add an opaque-only wrapper-name tag so guarded readers reject incompatible records and callbacks use the Python fallback. Leave full-API and ordinary abi3 names unchanged to retain compatible cross-module fast paths. Neither the C++ function_record ABI nor PYBIND11_INTERNALS_VERSION changes. The regression checks both mixed directions and recognition between compatible foreign modules. It fails before the fix even when CPython's current layout happens to produce the correct callback result. Validated with C++11 abi3 and abi3t integration builds (GIL-enabled and free-threaded), 69 focused tests in each CPython 3.14 environment, and all pre-commit checks.
— codex GPT-6 on behalf of Ralf — Use _abi3 and _abi3t instead of _stable and _stable_ft. PEP 803 uses abi3 and abi3t for the two Python stable ABI targets; reusing those names removes an unnecessary terminology mapping for readers and maintainers. https://peps.python.org/pep-0803/ The names are also more precise: _stable_ft can suggest the interpreter's current threading mode, but an abi3t module keeps its compiled internals representation when running on a GIL-enabled interpreter. Settle the registry-key spelling while this support is still under review, before it becomes a released compatibility commitment. Apply the rename to both global and module-local internals IDs and update the documentation. Ordinary IDs, platform/conduit ABI IDs, and PYBIND11_INTERNALS_VERSION stay unchanged. Validated with C++11 abi3 and abi3t mixed-module integration checks on GIL-enabled and free-threaded Python, plus all pre-commit checks.
rwgk
left a comment
There was a problem hiding this comment.
I'm done with my codex-guided walk-through: LGTM!
— codex GPT-6 on behalf of Ralf — Merge master at 8b9619b, including pybind#6198 and the chrono lookup fix. Keep pybind#6249's owning get_object_handle() return in both representations. The abi3t path transfers the matching snapshot reference; the full-API path retains try_incref() under the instance-map lock. Preserve the owning self in get_type_override(). Retain the probe's NO_PRECOMPILE exemption alongside the stable-ABI test setup. Its PyMutex_Unlock instrumentation requires the full API, so guard it accordingly and explicitly skip those two probes under the Limited API. Validated focused thread/virtual tests: 15 passed, 4 skipped on default CPython 3.14; 19 passed on free-threaded 3.14; 15 passed, 4 skipped on free-threaded abi3t (C++11, CPython 3.15.0b2).


Only build one wheel for 3.12+ and 3.15t+ per platform and support all future versions of Python.
Implemented with Fable, added abi3t, got numpy working, got chrono working, iterated with linked Claude sessions on several downstream projects (finding both fixes and performance optimizations to keep this from being to costly), simplified with Fable, and reviewed with Astra, which found five issues. This was originally following #6104, with a commit for each "PR" in that roadmap, but there was quite a bit of iteration, so I opened this as one PR. I'm thinking it might be preferred to review this way these days anyway.
This supports abi3t on 3.15+ and abi3 on 3.12+. Original performance was pretty bad, but optimized a bit and some parts are faster than the unstable ABI (though those optimizations can of course be applied there too later). It depends very much on what you are doing (some things are slower with stable ABI, like attribute access with no call), but for most projects, this should be performant enough. awkward showed a 1-3% drop for one microbenchmark, otherwise unmeasurable. After fixes, iminuit shows no measurable slowdown (one microbenchmark is 17% faster due to an optimization to method-descriptor calls we could also apply to non-stable ABI builds).
Questions:
🤖 AI text below 🤖
Description
Adds support for the CPython stable ABI (
Py_LIMITED_API >= 0x030C0000, abi3) and the free-threaded stable ABI (abi3t, CPython 3.15+, PEP 803). One*.abi3.soper platform then runs on every CPython from the targeted version on.Draft: opened so that others can build their projects against it. Tried so far on boost-histogram, iminuit, and awkward. PyTorch can't use it, due to custom code calling C-API functions, but checked to make sure it still works with the mode turned off.
How to try it:
pybind11_add_module(example STABLE_ABI example.cpp)or-DPYBIND11_STABLE_ABI=ON(CMake 3.26+, FindPython mode).PYBIND11_STABLE_ABI_VERSIONselects the targeted version (default 3.12; 3.15 on free-threaded Python).Pybind11Extension(..., py_limited_api=True).Py_LIMITED_API=0x030C0000and name the module*.abi3.so.What changes:
PyType_SpecwithPyType_FromMetaclass(). This path is also available to regular builds on 3.12+ as thePYBIND11_TYPE_CREATION_VIA_SPECopt-in. (Edit: now opt-out)PyType_GetSlot(),PyObject_GetTypeData(), checked tuple/list accessors,__mro__/__dict__lookups).instancemethodtype, which is a vectorcall method descriptor, soobj.method(...)does not create a bound method.chrono.huses thedatetimetypes by attribute;numpy.hunder abi3t needs NumPy 2.5+.embed.h,subinterpreter.h,py::custom_type_setup, GIL-held assertions;py::metaclass()is restricted. Seedocs/advanced/stable_abi.rstfor the full table._abi3/_abi3ttag);pybind11_conduit_v1bridges them.CI builds and runs the test suite for abi3 on 3.12 to 3.14 and abi3t on 3.15t, and runs the 3.12 abi3 binaries on 3.13 and 3.14 and the 3.15t abi3t binaries on GIL-enabled 3.15.
Suggested changelog entry:
Py_LIMITED_API >= 0x030C0000) and the free-threaded stable ABI (abi3t, CPython 3.15+). Enable withSTABLE_ABIinpybind11_add_module(),py_limited_api=TrueinPybind11Extension, or by definingPy_LIMITED_API.PyType_FromMetaclass()by default on CPython 3.12+. DefinePYBIND11_TYPE_CREATION_VIA_SPEC=0(CMake-DPYBIND11_TYPE_CREATION_VIA_SPEC=OFF) to restore the previous path.📚 Documentation preview 📚: https://pybind11--6198.org.readthedocs.build/