From b736295ec56d4ccbeb18537ee25fbc7979b31675 Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Mon, 2 Mar 2026 12:55:10 -0700 Subject: [PATCH 1/5] allow limited API builds on free-threaded 3.15 and newer --- src/cffi/setuptools_ext.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/cffi/setuptools_ext.py b/src/cffi/setuptools_ext.py index 5cdd246f..5c0b7fe3 100644 --- a/src/cffi/setuptools_ext.py +++ b/src/cffi/setuptools_ext.py @@ -104,10 +104,9 @@ def _set_py_limited_api(Extension, kwds): # warning. kwds['py_limited_api'] = True - if sysconfig.get_config_var("Py_GIL_DISABLED"): + if sysconfig.get_config_var("Py_GIL_DISABLED") and sys.version_info < (3, 15): if kwds.get('py_limited_api'): log.info("Ignoring py_limited_api=True for free-threaded build.") - kwds['py_limited_api'] = False if kwds.get('py_limited_api') is False: From 5f76ffb127a5eba601994107d4779c1f92a844ba Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Mon, 2 Mar 2026 13:42:41 -0700 Subject: [PATCH 2/5] set _Py_OPAQUE_PYOBJECT for limited API free-threaded builds --- src/cffi/recompiler.py | 2 +- src/cffi/setuptools_ext.py | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/cffi/recompiler.py b/src/cffi/recompiler.py index 83ee5c7d..6f2debe5 100644 --- a/src/cffi/recompiler.py +++ b/src/cffi/recompiler.py @@ -9,7 +9,7 @@ USE_LIMITED_API = ((sys.platform != 'win32' or sys.version_info < (3, 0) or sys.version_info >= (3, 5)) and - not sysconfig.get_config_var("Py_GIL_DISABLED")) # free-threaded doesn't yet support limited API + (not sysconfig.get_config_var("Py_GIL_DISABLED") or sys.version_info >= (3, 15))) # free-threaded doesn't yet support limited API class GlobalExpr: def __init__(self, name, address, type_op, size=0, check_value=0): diff --git a/src/cffi/setuptools_ext.py b/src/cffi/setuptools_ext.py index 5c0b7fe3..fadec63d 100644 --- a/src/cffi/setuptools_ext.py +++ b/src/cffi/setuptools_ext.py @@ -104,10 +104,13 @@ def _set_py_limited_api(Extension, kwds): # warning. kwds['py_limited_api'] = True - if sysconfig.get_config_var("Py_GIL_DISABLED") and sys.version_info < (3, 15): - if kwds.get('py_limited_api'): - log.info("Ignoring py_limited_api=True for free-threaded build.") - kwds['py_limited_api'] = False + if sysconfig.get_config_var("Py_GIL_DISABLED"): + if sys.version_info < (3, 15): + if kwds.get('py_limited_api'): + log.info("Ignoring py_limited_api=True for free-threaded build.") + kwds['py_limited_api'] = False + else: + kwds.setdefault("define_macros", []).append(("_Py_OPAQUE_PYOBJECT", None)) if kwds.get('py_limited_api') is False: # avoid setting Py_LIMITED_API if py_limited_api=False From 017ea83ee5eabcbc143202122db44d9aa5971a96 Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Mon, 2 Mar 2026 14:07:36 -0700 Subject: [PATCH 3/5] define Py_LIMITED_API manually. This seems questionable --- src/cffi/setuptools_ext.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cffi/setuptools_ext.py b/src/cffi/setuptools_ext.py index fadec63d..225d4cdc 100644 --- a/src/cffi/setuptools_ext.py +++ b/src/cffi/setuptools_ext.py @@ -111,6 +111,7 @@ def _set_py_limited_api(Extension, kwds): kwds['py_limited_api'] = False else: kwds.setdefault("define_macros", []).append(("_Py_OPAQUE_PYOBJECT", None)) + kwds["define_macros"].append(("Py_LIMITED_API", "0x030f0000")) if kwds.get('py_limited_api') is False: # avoid setting Py_LIMITED_API if py_limited_api=False From 69d1fa40aa9372eff16aa97791b5f7234a01e871 Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Thu, 16 Apr 2026 08:29:55 -0600 Subject: [PATCH 4/5] Use 3.15t for free-threaded CI --- .github/workflows/ci.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index fd5b36e9..374e8ba9 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -504,9 +504,9 @@ jobs: with: matrix_yaml: | include: - - { runner: ubuntu-24.04, python-version: 3.14t } - - { runner: macos-26, python-version: 3.14t } - - { runner: windows-2025, python-version: 3.14t } + - { runner: ubuntu-24.04, python-version: 3.15t-dev } + - { runner: macos-26, python-version: 3.15t-dev } + - { runner: windows-2025, python-version: 3.15t-dev } pytest-run-parallel: needs: make_run_parallel_matrix @@ -536,7 +536,7 @@ jobs: clang_TSAN: runs-on: ubuntu-24.04 - container: ghcr.io/nascheme/numpy-tsan:3.14t + container: ghcr.io/nascheme/numpy-tsan:3.15t-dev steps: - uses: actions/checkout@v4 From 904e261c6edaa11cbb7e4943378a3cb0a5daae6b Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Thu, 16 Apr 2026 09:06:33 -0600 Subject: [PATCH 5/5] Update for merged PEP 803 implementation --- src/cffi/_cffi_include.h | 13 +++++++++++-- src/cffi/setuptools_ext.py | 3 +-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/cffi/_cffi_include.h b/src/cffi/_cffi_include.h index c7186307..c8c6d343 100644 --- a/src/cffi/_cffi_include.h +++ b/src/cffi/_cffi_include.h @@ -28,8 +28,13 @@ #if !defined(_CFFI_USE_EMBEDDING) && !defined(Py_LIMITED_API) # ifdef _MSC_VER # if !defined(_DEBUG) && !defined(Py_DEBUG) && !defined(Py_TRACE_REFS) && !defined(Py_REF_DEBUG) && !defined(_CFFI_NO_LIMITED_API) -# define Py_LIMITED_API +# if !defined(Py_GIL_DISABLED) +# define Py_LIMITED_API +# else +# define Py_LIMITED_API 0x030f0000 +# endif # endif + # include /* sanity-check: Py_LIMITED_API will cause crashes if any of these are also defined. Normally, the Python file PC/pyconfig.h does not @@ -49,7 +54,11 @@ # else # include # if !defined(Py_DEBUG) && !defined(Py_TRACE_REFS) && !defined(Py_REF_DEBUG) && !defined(_CFFI_NO_LIMITED_API) -# define Py_LIMITED_API +# if !defined(Py_GIL_DISABLED) +# define Py_LIMITED_API +# else +# define Py_LIMITED_API 0x030f0000 +# endif # endif # endif #endif diff --git a/src/cffi/setuptools_ext.py b/src/cffi/setuptools_ext.py index 225d4cdc..319ab0f2 100644 --- a/src/cffi/setuptools_ext.py +++ b/src/cffi/setuptools_ext.py @@ -110,8 +110,7 @@ def _set_py_limited_api(Extension, kwds): log.info("Ignoring py_limited_api=True for free-threaded build.") kwds['py_limited_api'] = False else: - kwds.setdefault("define_macros", []).append(("_Py_OPAQUE_PYOBJECT", None)) - kwds["define_macros"].append(("Py_LIMITED_API", "0x030f0000")) + kwds["define_macros"].append(("Py_TARGET_ABI3T", "0x030f0000")) if kwds.get('py_limited_api') is False: # avoid setting Py_LIMITED_API if py_limited_api=False