From 5c25a54524de5d36832a11cfc01ffaf67a3b32fa Mon Sep 17 00:00:00 2001 From: Florentin Labelle Date: Wed, 15 Jul 2026 13:39:13 +0200 Subject: [PATCH 1/5] feat(ffi): add Python 3.14 context watcher API --- newsfragments/6204.added.md | 1 + pyo3-ffi/src/context.rs | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 newsfragments/6204.added.md diff --git a/newsfragments/6204.added.md b/newsfragments/6204.added.md new file mode 100644 index 00000000000..2e7f07886e9 --- /dev/null +++ b/newsfragments/6204.added.md @@ -0,0 +1 @@ +Add the Python 3.14 context watcher API to `pyo3-ffi`. diff --git a/pyo3-ffi/src/context.rs b/pyo3-ffi/src/context.rs index 5defd44dbfa..962b05e789d 100644 --- a/pyo3-ffi/src/context.rs +++ b/pyo3-ffi/src/context.rs @@ -3,8 +3,21 @@ use crate::object::PyObject; use crate::object::PyTypeObject; #[cfg(not(RustPython))] use crate::Py_IS_TYPE; +#[cfg(all(Py_3_14, not(any(PyPy, GraalPy, Py_LIMITED_API))))] +use core::ffi::c_uint; use core::ffi::{c_char, c_int}; +// Use the C enum's integer representation to permit future event values. +#[cfg(all(Py_3_14, not(any(PyPy, GraalPy, Py_LIMITED_API))))] +pub type PyContextEvent = c_uint; + +#[cfg(all(Py_3_14, not(any(PyPy, GraalPy, Py_LIMITED_API))))] +pub const Py_CONTEXT_SWITCHED: PyContextEvent = 1; + +#[cfg(all(Py_3_14, not(any(PyPy, GraalPy, Py_LIMITED_API))))] +pub type PyContext_WatchCallback = + unsafe extern "C" fn(event: PyContextEvent, obj: *mut PyObject) -> c_int; + #[cfg(not(RustPython))] extern_libpython! { pub static mut PyContext_Type: PyTypeObject; @@ -48,6 +61,11 @@ extern_libpython! { pub fn PyContext_Enter(ctx: *mut PyObject) -> c_int; pub fn PyContext_Exit(ctx: *mut PyObject) -> c_int; + #[cfg(all(Py_3_14, not(any(PyPy, GraalPy, Py_LIMITED_API))))] + pub fn PyContext_AddWatcher(callback: PyContext_WatchCallback) -> c_int; + #[cfg(all(Py_3_14, not(any(PyPy, GraalPy, Py_LIMITED_API))))] + pub fn PyContext_ClearWatcher(watcher_id: c_int) -> c_int; + pub fn PyContextVar_New(name: *const c_char, def: *mut PyObject) -> *mut PyObject; pub fn PyContextVar_Get( var: *mut PyObject, From ed420e5f6fabd8a09ba51fa4747e1615a46806ec Mon Sep 17 00:00:00 2001 From: Florentin Labelle Date: Wed, 15 Jul 2026 16:24:30 +0200 Subject: [PATCH 2/5] refactor(ffi): move context bindings to cpython module --- pyo3-ffi/src/{ => cpython}/context.rs | 25 ++++++------------------- pyo3-ffi/src/cpython/mod.rs | 2 ++ pyo3-ffi/src/lib.rs | 4 ---- 3 files changed, 8 insertions(+), 23 deletions(-) rename pyo3-ffi/src/{ => cpython}/context.rs (71%) diff --git a/pyo3-ffi/src/context.rs b/pyo3-ffi/src/cpython/context.rs similarity index 71% rename from pyo3-ffi/src/context.rs rename to pyo3-ffi/src/cpython/context.rs index 962b05e789d..f11aad63c58 100644 --- a/pyo3-ffi/src/context.rs +++ b/pyo3-ffi/src/cpython/context.rs @@ -1,24 +1,21 @@ use crate::object::PyObject; -#[cfg(not(RustPython))] use crate::object::PyTypeObject; -#[cfg(not(RustPython))] use crate::Py_IS_TYPE; -#[cfg(all(Py_3_14, not(any(PyPy, GraalPy, Py_LIMITED_API))))] +#[cfg(all(Py_3_14, not(any(PyPy, GraalPy))))] use core::ffi::c_uint; use core::ffi::{c_char, c_int}; // Use the C enum's integer representation to permit future event values. -#[cfg(all(Py_3_14, not(any(PyPy, GraalPy, Py_LIMITED_API))))] +#[cfg(all(Py_3_14, not(any(PyPy, GraalPy))))] pub type PyContextEvent = c_uint; -#[cfg(all(Py_3_14, not(any(PyPy, GraalPy, Py_LIMITED_API))))] +#[cfg(all(Py_3_14, not(any(PyPy, GraalPy))))] pub const Py_CONTEXT_SWITCHED: PyContextEvent = 1; -#[cfg(all(Py_3_14, not(any(PyPy, GraalPy, Py_LIMITED_API))))] +#[cfg(all(Py_3_14, not(any(PyPy, GraalPy))))] pub type PyContext_WatchCallback = unsafe extern "C" fn(event: PyContextEvent, obj: *mut PyObject) -> c_int; -#[cfg(not(RustPython))] extern_libpython! { pub static mut PyContext_Type: PyTypeObject; // skipped non-limited opaque PyContext @@ -29,31 +26,21 @@ extern_libpython! { } #[inline] -#[cfg(not(RustPython))] pub unsafe fn PyContext_CheckExact(op: *mut PyObject) -> c_int { Py_IS_TYPE(op, &raw mut PyContext_Type) } #[inline] -#[cfg(not(RustPython))] pub unsafe fn PyContextVar_CheckExact(op: *mut PyObject) -> c_int { Py_IS_TYPE(op, &raw mut PyContextVar_Type) } #[inline] -#[cfg(not(RustPython))] pub unsafe fn PyContextToken_CheckExact(op: *mut PyObject) -> c_int { Py_IS_TYPE(op, &raw mut PyContextToken_Type) } extern_libpython! { - #[cfg(RustPython)] - pub fn PyContext_CheckExact(op: *mut PyObject) -> c_int; - #[cfg(RustPython)] - pub fn PyContextVar_CheckExact(op: *mut PyObject) -> c_int; - #[cfg(RustPython)] - pub fn PyContextToken_CheckExact(op: *mut PyObject) -> c_int; - pub fn PyContext_New() -> *mut PyObject; pub fn PyContext_Copy(ctx: *mut PyObject) -> *mut PyObject; pub fn PyContext_CopyCurrent() -> *mut PyObject; @@ -61,9 +48,9 @@ extern_libpython! { pub fn PyContext_Enter(ctx: *mut PyObject) -> c_int; pub fn PyContext_Exit(ctx: *mut PyObject) -> c_int; - #[cfg(all(Py_3_14, not(any(PyPy, GraalPy, Py_LIMITED_API))))] + #[cfg(all(Py_3_14, not(any(PyPy, GraalPy))))] pub fn PyContext_AddWatcher(callback: PyContext_WatchCallback) -> c_int; - #[cfg(all(Py_3_14, not(any(PyPy, GraalPy, Py_LIMITED_API))))] + #[cfg(all(Py_3_14, not(any(PyPy, GraalPy))))] pub fn PyContext_ClearWatcher(watcher_id: c_int) -> c_int; pub fn PyContextVar_New(name: *const c_char, def: *mut PyObject) -> *mut PyObject; diff --git a/pyo3-ffi/src/cpython/mod.rs b/pyo3-ffi/src/cpython/mod.rs index 5a432d4d493..3761c1e5439 100644 --- a/pyo3-ffi/src/cpython/mod.rs +++ b/pyo3-ffi/src/cpython/mod.rs @@ -7,6 +7,7 @@ pub(crate) mod ceval; pub(crate) mod code; pub(crate) mod compile; pub(crate) mod complexobject; +pub(crate) mod context; #[cfg(all(Py_3_14, Py_GIL_DISABLED))] pub(crate) mod critical_section; pub(crate) mod descrobject; @@ -56,6 +57,7 @@ pub use self::ceval::*; pub use self::code::*; pub use self::compile::*; pub use self::complexobject::*; +pub use self::context::*; #[cfg(all(Py_3_14, Py_GIL_DISABLED))] pub use self::critical_section::{PyCriticalSection2_BeginMutex, PyCriticalSection_BeginMutex}; pub use self::descrobject::*; diff --git a/pyo3-ffi/src/lib.rs b/pyo3-ffi/src/lib.rs index 04048f287eb..7fdf12a0094 100644 --- a/pyo3-ffi/src/lib.rs +++ b/pyo3-ffi/src/lib.rs @@ -446,8 +446,6 @@ pub use self::ceval::*; pub use self::codecs::*; pub use self::compile::*; pub use self::complexobject::*; -#[cfg(not(Py_LIMITED_API))] -pub use self::context::*; #[cfg(Py_3_13)] pub use self::critical_section::*; #[cfg(not(Py_LIMITED_API))] @@ -516,8 +514,6 @@ mod ceval; mod codecs; mod compile; mod complexobject; -#[cfg(not(Py_LIMITED_API))] -mod context; mod critical_section; #[cfg(not(Py_LIMITED_API))] pub(crate) mod datetime; From d4fc1c7da2592136457eb0059e6d70c73f41f858 Mon Sep 17 00:00:00 2001 From: Florentin Labelle Date: Wed, 15 Jul 2026 16:52:02 +0200 Subject: [PATCH 3/5] refactor(ffi): preserve context declarations in move --- pyo3-ffi/src/cpython/context.rs | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/pyo3-ffi/src/cpython/context.rs b/pyo3-ffi/src/cpython/context.rs index f11aad63c58..962b05e789d 100644 --- a/pyo3-ffi/src/cpython/context.rs +++ b/pyo3-ffi/src/cpython/context.rs @@ -1,21 +1,24 @@ use crate::object::PyObject; +#[cfg(not(RustPython))] use crate::object::PyTypeObject; +#[cfg(not(RustPython))] use crate::Py_IS_TYPE; -#[cfg(all(Py_3_14, not(any(PyPy, GraalPy))))] +#[cfg(all(Py_3_14, not(any(PyPy, GraalPy, Py_LIMITED_API))))] use core::ffi::c_uint; use core::ffi::{c_char, c_int}; // Use the C enum's integer representation to permit future event values. -#[cfg(all(Py_3_14, not(any(PyPy, GraalPy))))] +#[cfg(all(Py_3_14, not(any(PyPy, GraalPy, Py_LIMITED_API))))] pub type PyContextEvent = c_uint; -#[cfg(all(Py_3_14, not(any(PyPy, GraalPy))))] +#[cfg(all(Py_3_14, not(any(PyPy, GraalPy, Py_LIMITED_API))))] pub const Py_CONTEXT_SWITCHED: PyContextEvent = 1; -#[cfg(all(Py_3_14, not(any(PyPy, GraalPy))))] +#[cfg(all(Py_3_14, not(any(PyPy, GraalPy, Py_LIMITED_API))))] pub type PyContext_WatchCallback = unsafe extern "C" fn(event: PyContextEvent, obj: *mut PyObject) -> c_int; +#[cfg(not(RustPython))] extern_libpython! { pub static mut PyContext_Type: PyTypeObject; // skipped non-limited opaque PyContext @@ -26,21 +29,31 @@ extern_libpython! { } #[inline] +#[cfg(not(RustPython))] pub unsafe fn PyContext_CheckExact(op: *mut PyObject) -> c_int { Py_IS_TYPE(op, &raw mut PyContext_Type) } #[inline] +#[cfg(not(RustPython))] pub unsafe fn PyContextVar_CheckExact(op: *mut PyObject) -> c_int { Py_IS_TYPE(op, &raw mut PyContextVar_Type) } #[inline] +#[cfg(not(RustPython))] pub unsafe fn PyContextToken_CheckExact(op: *mut PyObject) -> c_int { Py_IS_TYPE(op, &raw mut PyContextToken_Type) } extern_libpython! { + #[cfg(RustPython)] + pub fn PyContext_CheckExact(op: *mut PyObject) -> c_int; + #[cfg(RustPython)] + pub fn PyContextVar_CheckExact(op: *mut PyObject) -> c_int; + #[cfg(RustPython)] + pub fn PyContextToken_CheckExact(op: *mut PyObject) -> c_int; + pub fn PyContext_New() -> *mut PyObject; pub fn PyContext_Copy(ctx: *mut PyObject) -> *mut PyObject; pub fn PyContext_CopyCurrent() -> *mut PyObject; @@ -48,9 +61,9 @@ extern_libpython! { pub fn PyContext_Enter(ctx: *mut PyObject) -> c_int; pub fn PyContext_Exit(ctx: *mut PyObject) -> c_int; - #[cfg(all(Py_3_14, not(any(PyPy, GraalPy))))] + #[cfg(all(Py_3_14, not(any(PyPy, GraalPy, Py_LIMITED_API))))] pub fn PyContext_AddWatcher(callback: PyContext_WatchCallback) -> c_int; - #[cfg(all(Py_3_14, not(any(PyPy, GraalPy))))] + #[cfg(all(Py_3_14, not(any(PyPy, GraalPy, Py_LIMITED_API))))] pub fn PyContext_ClearWatcher(watcher_id: c_int) -> c_int; pub fn PyContextVar_New(name: *const c_char, def: *mut PyObject) -> *mut PyObject; From 454b7588ba92d0a7a086b77a73b48c75e4ea5d99 Mon Sep 17 00:00:00 2001 From: Florentin Labelle Date: Wed, 15 Jul 2026 16:52:02 +0200 Subject: [PATCH 4/5] refactor(ffi): preserve RustPython context bindings --- pyo3-ffi/src/cpython/context.rs | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/pyo3-ffi/src/cpython/context.rs b/pyo3-ffi/src/cpython/context.rs index 962b05e789d..f11aad63c58 100644 --- a/pyo3-ffi/src/cpython/context.rs +++ b/pyo3-ffi/src/cpython/context.rs @@ -1,24 +1,21 @@ use crate::object::PyObject; -#[cfg(not(RustPython))] use crate::object::PyTypeObject; -#[cfg(not(RustPython))] use crate::Py_IS_TYPE; -#[cfg(all(Py_3_14, not(any(PyPy, GraalPy, Py_LIMITED_API))))] +#[cfg(all(Py_3_14, not(any(PyPy, GraalPy))))] use core::ffi::c_uint; use core::ffi::{c_char, c_int}; // Use the C enum's integer representation to permit future event values. -#[cfg(all(Py_3_14, not(any(PyPy, GraalPy, Py_LIMITED_API))))] +#[cfg(all(Py_3_14, not(any(PyPy, GraalPy))))] pub type PyContextEvent = c_uint; -#[cfg(all(Py_3_14, not(any(PyPy, GraalPy, Py_LIMITED_API))))] +#[cfg(all(Py_3_14, not(any(PyPy, GraalPy))))] pub const Py_CONTEXT_SWITCHED: PyContextEvent = 1; -#[cfg(all(Py_3_14, not(any(PyPy, GraalPy, Py_LIMITED_API))))] +#[cfg(all(Py_3_14, not(any(PyPy, GraalPy))))] pub type PyContext_WatchCallback = unsafe extern "C" fn(event: PyContextEvent, obj: *mut PyObject) -> c_int; -#[cfg(not(RustPython))] extern_libpython! { pub static mut PyContext_Type: PyTypeObject; // skipped non-limited opaque PyContext @@ -29,31 +26,21 @@ extern_libpython! { } #[inline] -#[cfg(not(RustPython))] pub unsafe fn PyContext_CheckExact(op: *mut PyObject) -> c_int { Py_IS_TYPE(op, &raw mut PyContext_Type) } #[inline] -#[cfg(not(RustPython))] pub unsafe fn PyContextVar_CheckExact(op: *mut PyObject) -> c_int { Py_IS_TYPE(op, &raw mut PyContextVar_Type) } #[inline] -#[cfg(not(RustPython))] pub unsafe fn PyContextToken_CheckExact(op: *mut PyObject) -> c_int { Py_IS_TYPE(op, &raw mut PyContextToken_Type) } extern_libpython! { - #[cfg(RustPython)] - pub fn PyContext_CheckExact(op: *mut PyObject) -> c_int; - #[cfg(RustPython)] - pub fn PyContextVar_CheckExact(op: *mut PyObject) -> c_int; - #[cfg(RustPython)] - pub fn PyContextToken_CheckExact(op: *mut PyObject) -> c_int; - pub fn PyContext_New() -> *mut PyObject; pub fn PyContext_Copy(ctx: *mut PyObject) -> *mut PyObject; pub fn PyContext_CopyCurrent() -> *mut PyObject; @@ -61,9 +48,9 @@ extern_libpython! { pub fn PyContext_Enter(ctx: *mut PyObject) -> c_int; pub fn PyContext_Exit(ctx: *mut PyObject) -> c_int; - #[cfg(all(Py_3_14, not(any(PyPy, GraalPy, Py_LIMITED_API))))] + #[cfg(all(Py_3_14, not(any(PyPy, GraalPy))))] pub fn PyContext_AddWatcher(callback: PyContext_WatchCallback) -> c_int; - #[cfg(all(Py_3_14, not(any(PyPy, GraalPy, Py_LIMITED_API))))] + #[cfg(all(Py_3_14, not(any(PyPy, GraalPy))))] pub fn PyContext_ClearWatcher(watcher_id: c_int) -> c_int; pub fn PyContextVar_New(name: *const c_char, def: *mut PyObject) -> *mut PyObject; From 25e660533537c6f1f0ac73532272d93317d29065 Mon Sep 17 00:00:00 2001 From: Florentin Labelle Date: Mon, 20 Jul 2026 10:48:29 +0200 Subject: [PATCH 5/5] reorder declarations to follow cpython header file --- newsfragments/6204.added.md | 2 +- pyo3-ffi/src/cpython/context.rs | 24 +++++++++++++----------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/newsfragments/6204.added.md b/newsfragments/6204.added.md index 2e7f07886e9..ebb7b791943 100644 --- a/newsfragments/6204.added.md +++ b/newsfragments/6204.added.md @@ -1 +1 @@ -Add the Python 3.14 context watcher API to `pyo3-ffi`. +Add FFI definitions `PyContext_AddWatcher` and `PyContext_ClearWatcher` on Python 3.14+. diff --git a/pyo3-ffi/src/cpython/context.rs b/pyo3-ffi/src/cpython/context.rs index f11aad63c58..ef22c4395a5 100644 --- a/pyo3-ffi/src/cpython/context.rs +++ b/pyo3-ffi/src/cpython/context.rs @@ -5,17 +5,6 @@ use crate::Py_IS_TYPE; use core::ffi::c_uint; use core::ffi::{c_char, c_int}; -// Use the C enum's integer representation to permit future event values. -#[cfg(all(Py_3_14, not(any(PyPy, GraalPy))))] -pub type PyContextEvent = c_uint; - -#[cfg(all(Py_3_14, not(any(PyPy, GraalPy))))] -pub const Py_CONTEXT_SWITCHED: PyContextEvent = 1; - -#[cfg(all(Py_3_14, not(any(PyPy, GraalPy))))] -pub type PyContext_WatchCallback = - unsafe extern "C" fn(event: PyContextEvent, obj: *mut PyObject) -> c_int; - extern_libpython! { pub static mut PyContext_Type: PyTypeObject; // skipped non-limited opaque PyContext @@ -47,7 +36,20 @@ extern_libpython! { pub fn PyContext_Enter(ctx: *mut PyObject) -> c_int; pub fn PyContext_Exit(ctx: *mut PyObject) -> c_int; +} +// Use the C enum's integer representation to permit future event values. +#[cfg(all(Py_3_14, not(any(PyPy, GraalPy))))] +pub type PyContextEvent = c_uint; + +#[cfg(all(Py_3_14, not(any(PyPy, GraalPy))))] +pub const Py_CONTEXT_SWITCHED: PyContextEvent = 1; + +#[cfg(all(Py_3_14, not(any(PyPy, GraalPy))))] +pub type PyContext_WatchCallback = + unsafe extern "C" fn(event: PyContextEvent, obj: *mut PyObject) -> c_int; + +extern_libpython! { #[cfg(all(Py_3_14, not(any(PyPy, GraalPy))))] pub fn PyContext_AddWatcher(callback: PyContext_WatchCallback) -> c_int; #[cfg(all(Py_3_14, not(any(PyPy, GraalPy))))]