Merge pull request #3770 from Icxolu/capsule

implement `PyCapsuleMethods`
This commit is contained in:
David Hewitt 2024-01-29 21:23:43 +00:00 committed by GitHub
commit 718be9fac5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 214 additions and 75 deletions

View File

@ -29,6 +29,7 @@ pub use crate::types::any::PyAnyMethods;
pub use crate::types::boolobject::PyBoolMethods;
pub use crate::types::bytearray::PyByteArrayMethods;
pub use crate::types::bytes::PyBytesMethods;
pub use crate::types::capsule::PyCapsuleMethods;
pub use crate::types::dict::PyDictMethods;
pub use crate::types::float::PyFloatMethods;
pub use crate::types::frozenset::PyFrozenSetMethods;

View File

@ -1,6 +1,8 @@
use crate::Python;
use crate::{ffi, PyAny};
use crate::ffi_ptr_ext::FfiPtrExt;
use crate::py_result_ext::PyResultExt;
use crate::{ffi, PyAny, PyNativeType};
use crate::{pyobject_native_type_core, PyErr, PyResult};
use crate::{Bound, Python};
use std::ffi::{CStr, CString};
use std::os::raw::{c_char, c_int, c_void};
@ -27,7 +29,7 @@ use std::os::raw::{c_char, c_int, c_void};
/// let foo = Foo { val: 123 };
/// let name = CString::new("builtins.capsule").unwrap();
///
/// let capsule = PyCapsule::new(py, foo, Some(name.clone()))?;
/// let capsule = PyCapsule::new_bound(py, foo, Some(name.clone()))?;
///
/// let module = PyModule::import(py, "builtins")?;
/// module.add("capsule", capsule)?;
@ -44,6 +46,22 @@ pub struct PyCapsule(PyAny);
pyobject_native_type_core!(PyCapsule, pyobject_native_static_type_object!(ffi::PyCapsule_Type), #checkfunction=ffi::PyCapsule_CheckExact);
impl PyCapsule {
/// Deprecated form of [`PyCapsule::new_bound`].
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
since = "0.21.0",
note = "`PyCapsule::new` will be replaced by `PyCapsule::new_bound` in a future PyO3 version"
)
)]
pub fn new<T: 'static + Send + AssertNotZeroSized>(
py: Python<'_>,
value: T,
name: Option<CString>,
) -> PyResult<&Self> {
Self::new_bound(py, value, name).map(Bound::into_gil_ref)
}
/// Constructs a new capsule whose contents are `value`, associated with `name`.
/// `name` is the identifier for the capsule; if it is stored as an attribute of a module,
/// the name should be in the format `"modulename.attribute"`.
@ -59,7 +77,7 @@ impl PyCapsule {
///
/// Python::with_gil(|py| {
/// let name = CString::new("foo").unwrap();
/// let capsule = PyCapsule::new(py, 123_u32, Some(name)).unwrap();
/// let capsule = PyCapsule::new_bound(py, 123_u32, Some(name)).unwrap();
/// let val = unsafe { capsule.reference::<u32>() };
/// assert_eq!(*val, 123);
/// });
@ -72,24 +90,25 @@ impl PyCapsule {
/// use std::ffi::CString;
///
/// Python::with_gil(|py| {
/// let capsule = PyCapsule::new(py, (), None).unwrap(); // Oops! `()` is zero sized!
/// let capsule = PyCapsule::new_bound(py, (), None).unwrap(); // Oops! `()` is zero sized!
/// });
/// ```
pub fn new<T: 'static + Send + AssertNotZeroSized>(
pub fn new_bound<T: 'static + Send + AssertNotZeroSized>(
py: Python<'_>,
value: T,
name: Option<CString>,
) -> PyResult<&Self> {
Self::new_with_destructor(py, value, name, |_, _| {})
) -> PyResult<Bound<'_, Self>> {
Self::new_bound_with_destructor(py, value, name, |_, _| {})
}
/// Constructs a new capsule whose contents are `value`, associated with `name`.
///
/// Also provides a destructor: when the `PyCapsule` is destroyed, it will be passed the original object,
/// as well as a `*mut c_void` which will point to the capsule's context, if any.
///
/// The `destructor` must be `Send`, because there is no guarantee which thread it will eventually
/// be called from.
/// Deprecated form of [`PyCapsule::new_bound_with_destructor`].
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
since = "0.21.0",
note = "`PyCapsule::new_with_destructor` will be replaced by `PyCapsule::new_bound_with_destructor` in a future PyO3 version"
)
)]
pub fn new_with_destructor<
T: 'static + Send + AssertNotZeroSized,
F: FnOnce(T, *mut c_void) + Send,
@ -99,6 +118,25 @@ impl PyCapsule {
name: Option<CString>,
destructor: F,
) -> PyResult<&'_ Self> {
Self::new_bound_with_destructor(py, value, name, destructor).map(Bound::into_gil_ref)
}
/// Constructs a new capsule whose contents are `value`, associated with `name`.
///
/// Also provides a destructor: when the `PyCapsule` is destroyed, it will be passed the original object,
/// as well as a `*mut c_void` which will point to the capsule's context, if any.
///
/// The `destructor` must be `Send`, because there is no guarantee which thread it will eventually
/// be called from.
pub fn new_bound_with_destructor<
T: 'static + Send + AssertNotZeroSized,
F: FnOnce(T, *mut c_void) + Send,
>(
py: Python<'_>,
value: T,
name: Option<CString>,
destructor: F,
) -> PyResult<Bound<'_, Self>> {
AssertNotZeroSized::assert_not_zero_sized(&value);
// Sanity check for capsule layout
@ -112,12 +150,13 @@ impl PyCapsule {
});
unsafe {
let cap_ptr = ffi::PyCapsule_New(
Box::into_raw(val) as *mut c_void,
ffi::PyCapsule_New(
Box::into_raw(val).cast(),
name_ptr,
Some(capsule_destructor::<T, F>),
);
py.from_owned_ptr_or_err(cap_ptr)
)
.assume_owned_or_err(py)
.downcast_into_unchecked()
}
}
@ -134,7 +173,7 @@ impl PyCapsule {
if ptr.is_null() {
Err(PyErr::fetch(py))
} else {
Ok(&*(ptr as *const T))
Ok(&*ptr.cast::<T>())
}
}
@ -160,29 +199,23 @@ impl PyCapsule {
/// let (tx, rx) = channel::<String>();
///
/// fn destructor(val: u32, context: *mut c_void) {
/// let ctx = unsafe { *Box::from_raw(context as *mut Sender<String>) };
/// let ctx = unsafe { *Box::from_raw(context.cast::<Sender<String>>()) };
/// ctx.send("Destructor called!".to_string()).unwrap();
/// }
///
/// Python::with_gil(|py| {
/// let capsule =
/// PyCapsule::new_with_destructor(py, 123, None, destructor as fn(u32, *mut c_void))
/// PyCapsule::new_bound_with_destructor(py, 123, None, destructor as fn(u32, *mut c_void))
/// .unwrap();
/// let context = Box::new(tx); // `Sender<String>` is our context, box it up and ship it!
/// capsule.set_context(Box::into_raw(context) as *mut c_void).unwrap();
/// capsule.set_context(Box::into_raw(context).cast()).unwrap();
/// // This scope will end, causing our destructor to be called...
/// });
///
/// assert_eq!(rx.recv(), Ok("Destructor called!".to_string()));
/// ```
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn set_context(&self, context: *mut c_void) -> PyResult<()> {
let result = unsafe { ffi::PyCapsule_SetContext(self.as_ptr(), context) };
if result != 0 {
Err(PyErr::fetch(self.py()))
} else {
Ok(())
}
self.as_borrowed().set_context(context)
}
/// Gets the current context stored in the capsule. If there is no context, the pointer
@ -190,11 +223,7 @@ impl PyCapsule {
///
/// Returns an error if this capsule is not valid.
pub fn context(&self) -> PyResult<*mut c_void> {
let ctx = unsafe { ffi::PyCapsule_GetContext(self.as_ptr()) };
if ctx.is_null() {
ensure_no_error(self.py())?
}
Ok(ctx)
self.as_borrowed().context()
}
/// Obtains a reference to the value of this capsule.
@ -203,15 +232,132 @@ impl PyCapsule {
///
/// It must be known that this capsule is valid and its pointer is to an item of type `T`.
pub unsafe fn reference<T>(&self) -> &T {
&*(self.pointer() as *const T)
self.as_borrowed().reference()
}
/// Gets the raw `c_void` pointer to the value in this capsule.
///
/// Returns null if this capsule is not valid.
pub fn pointer(&self) -> *mut c_void {
self.as_borrowed().pointer()
}
/// Checks if this is a valid capsule.
///
/// Returns true if the stored `pointer()` is non-null.
pub fn is_valid(&self) -> bool {
self.as_borrowed().is_valid()
}
/// Retrieves the name of this capsule, if set.
///
/// Returns an error if this capsule is not valid.
pub fn name(&self) -> PyResult<Option<&CStr>> {
self.as_borrowed().name()
}
}
/// Implementation of functionality for [`PyCapsule`].
///
/// These methods are defined for the `Bound<'py, PyCapsule>` smart pointer, so to use method call
/// syntax these methods are separated into a trait, because stable Rust does not yet support
/// `arbitrary_self_types`.
#[doc(alias = "PyCapsule")]
pub trait PyCapsuleMethods<'py> {
/// Sets the context pointer in the capsule.
///
/// Returns an error if this capsule is not valid.
///
/// # Notes
///
/// The context is treated much like the value of the capsule, but should likely act as
/// a place to store any state management when using the capsule.
///
/// If you want to store a Rust value as the context, and drop it from the destructor, use
/// `Box::into_raw` to convert it into a pointer, see the example.
///
/// # Example
///
/// ```
/// use std::sync::mpsc::{channel, Sender};
/// use libc::c_void;
/// use pyo3::{prelude::*, types::PyCapsule};
///
/// let (tx, rx) = channel::<String>();
///
/// fn destructor(val: u32, context: *mut c_void) {
/// let ctx = unsafe { *Box::from_raw(context.cast::<Sender<String>>()) };
/// ctx.send("Destructor called!".to_string()).unwrap();
/// }
///
/// Python::with_gil(|py| {
/// let capsule =
/// PyCapsule::new_bound_with_destructor(py, 123, None, destructor as fn(u32, *mut c_void))
/// .unwrap();
/// let context = Box::new(tx); // `Sender<String>` is our context, box it up and ship it!
/// capsule.set_context(Box::into_raw(context).cast()).unwrap();
/// // This scope will end, causing our destructor to be called...
/// });
///
/// assert_eq!(rx.recv(), Ok("Destructor called!".to_string()));
/// ```
fn set_context(&self, context: *mut c_void) -> PyResult<()>;
/// Gets the current context stored in the capsule. If there is no context, the pointer
/// will be null.
///
/// Returns an error if this capsule is not valid.
fn context(&self) -> PyResult<*mut c_void>;
/// Obtains a reference to the value of this capsule.
///
/// # Safety
///
/// It must be known that this capsule is valid and its pointer is to an item of type `T`.
unsafe fn reference<T>(&self) -> &'py T;
/// Gets the raw `c_void` pointer to the value in this capsule.
///
/// Returns null if this capsule is not valid.
fn pointer(&self) -> *mut c_void;
/// Checks if this is a valid capsule.
///
/// Returns true if the stored `pointer()` is non-null.
fn is_valid(&self) -> bool;
/// Retrieves the name of this capsule, if set.
///
/// Returns an error if this capsule is not valid.
fn name(&self) -> PyResult<Option<&'py CStr>>;
}
impl<'py> PyCapsuleMethods<'py> for Bound<'py, PyCapsule> {
#[allow(clippy::not_unsafe_ptr_arg_deref)]
fn set_context(&self, context: *mut c_void) -> PyResult<()> {
let result = unsafe { ffi::PyCapsule_SetContext(self.as_ptr(), context) };
if result != 0 {
Err(PyErr::fetch(self.py()))
} else {
Ok(())
}
}
fn context(&self) -> PyResult<*mut c_void> {
let ctx = unsafe { ffi::PyCapsule_GetContext(self.as_ptr()) };
if ctx.is_null() {
ensure_no_error(self.py())?
}
Ok(ctx)
}
unsafe fn reference<T>(&self) -> &'py T {
&*self.pointer().cast()
}
fn pointer(&self) -> *mut c_void {
unsafe {
let ptr = ffi::PyCapsule_GetPointer(self.0.as_ptr(), self.name_ptr_ignore_error());
let ptr = ffi::PyCapsule_GetPointer(self.as_ptr(), name_ptr_ignore_error(self));
if ptr.is_null() {
ffi::PyErr_Clear();
}
@ -219,21 +365,15 @@ impl PyCapsule {
}
}
/// Checks if this is a valid capsule.
///
/// Returns true if the stored `pointer()` is non-null.
pub fn is_valid(&self) -> bool {
fn is_valid(&self) -> bool {
// As well as if the stored pointer is null, PyCapsule_IsValid also returns false if
// self.as_ptr() is null or not a ptr to a PyCapsule object. Both of these are guaranteed
// to not be the case thanks to invariants of this PyCapsule struct.
let r = unsafe { ffi::PyCapsule_IsValid(self.as_ptr(), self.name_ptr_ignore_error()) };
let r = unsafe { ffi::PyCapsule_IsValid(self.as_ptr(), name_ptr_ignore_error(self)) };
r != 0
}
/// Retrieves the name of this capsule, if set.
///
/// Returns an error if this capsule is not valid.
pub fn name(&self) -> PyResult<Option<&CStr>> {
fn name(&self) -> PyResult<Option<&'py CStr>> {
unsafe {
let ptr = ffi::PyCapsule_GetName(self.as_ptr());
if ptr.is_null() {
@ -244,18 +384,6 @@ impl PyCapsule {
}
}
}
/// Attempts to retrieve the raw name pointer of this capsule.
///
/// On error, clears the error indicator and returns NULL. This is a private function and next
/// use of this capsule will error anyway.
fn name_ptr_ignore_error(&self) -> *const c_char {
let ptr = unsafe { ffi::PyCapsule_GetName(self.as_ptr()) };
if ptr.is_null() {
unsafe { ffi::PyErr_Clear() };
}
ptr
}
}
// C layout, as PyCapsule::get_reference depends on `T` being first.
@ -277,7 +405,7 @@ unsafe extern "C" fn capsule_destructor<T: 'static + Send, F: FnOnce(T, *mut c_v
let ctx = ffi::PyCapsule_GetContext(capsule);
let CapsuleContents {
value, destructor, ..
} = *Box::from_raw(ptr as *mut CapsuleContents<T, F>);
} = *Box::from_raw(ptr.cast::<CapsuleContents<T, F>>());
destructor(value, ctx)
}
@ -304,11 +432,20 @@ fn ensure_no_error(py: Python<'_>) -> PyResult<()> {
}
}
fn name_ptr_ignore_error(slf: &Bound<'_, PyCapsule>) -> *const c_char {
let ptr = unsafe { ffi::PyCapsule_GetName(slf.as_ptr()) };
if ptr.is_null() {
unsafe { ffi::PyErr_Clear() };
}
ptr
}
#[cfg(test)]
mod tests {
use libc::c_void;
use crate::prelude::PyModule;
use crate::types::capsule::PyCapsuleMethods;
use crate::{types::PyCapsule, Py, PyResult, Python};
use std::ffi::CString;
use std::sync::mpsc::{channel, Sender};
@ -330,7 +467,7 @@ mod tests {
let foo = Foo { val: 123 };
let name = CString::new("foo").unwrap();
let cap = PyCapsule::new(py, foo, Some(name.clone()))?;
let cap = PyCapsule::new_bound(py, foo, Some(name.clone()))?;
assert!(cap.is_valid());
let foo_capi = unsafe { cap.reference::<Foo>() };
@ -349,7 +486,7 @@ mod tests {
let cap: Py<PyCapsule> = Python::with_gil(|py| {
let name = CString::new("foo").unwrap();
let cap = PyCapsule::new(py, foo as fn(u32) -> u32, Some(name)).unwrap();
let cap = PyCapsule::new_bound(py, foo as fn(u32) -> u32, Some(name)).unwrap();
cap.into()
});
@ -363,16 +500,16 @@ mod tests {
fn test_pycapsule_context() -> PyResult<()> {
Python::with_gil(|py| {
let name = CString::new("foo").unwrap();
let cap = PyCapsule::new(py, 0, Some(name))?;
let cap = PyCapsule::new_bound(py, 0, Some(name))?;
let c = cap.context()?;
assert!(c.is_null());
let ctx = Box::new(123_u32);
cap.set_context(Box::into_raw(ctx) as _)?;
cap.set_context(Box::into_raw(ctx).cast())?;
let ctx_ptr: *mut c_void = cap.context()?;
let ctx = unsafe { *Box::from_raw(ctx_ptr as *mut u32) };
let ctx = unsafe { *Box::from_raw(ctx_ptr.cast::<u32>()) };
assert_eq!(ctx, 123);
Ok(())
})
@ -389,7 +526,7 @@ mod tests {
let foo = Foo { val: 123 };
let name = CString::new("builtins.capsule").unwrap();
let capsule = PyCapsule::new(py, foo, Some(name.clone()))?;
let capsule = PyCapsule::new_bound(py, foo, Some(name.clone()))?;
let module = PyModule::import(py, "builtins")?;
module.add("capsule", capsule)?;
@ -412,7 +549,7 @@ mod tests {
let name = CString::new("foo").unwrap();
let stuff: Vec<u8> = vec![1, 2, 3, 4];
let cap = PyCapsule::new(py, stuff, Some(name)).unwrap();
let cap = PyCapsule::new_bound(py, stuff, Some(name)).unwrap();
cap.into()
});
@ -429,8 +566,8 @@ mod tests {
let cap: Py<PyCapsule> = Python::with_gil(|py| {
let name = CString::new("foo").unwrap();
let cap = PyCapsule::new(py, 0, Some(name)).unwrap();
cap.set_context(Box::into_raw(Box::new(&context)) as _)
let cap = PyCapsule::new_bound(py, 0, Some(name)).unwrap();
cap.set_context(Box::into_raw(Box::new(&context)).cast())
.unwrap();
cap.into()
@ -438,7 +575,7 @@ mod tests {
Python::with_gil(|py| {
let ctx_ptr: *mut c_void = cap.as_ref(py).context().unwrap();
let ctx = unsafe { *Box::from_raw(ctx_ptr as *mut &Vec<u8>) };
let ctx = unsafe { *Box::from_raw(ctx_ptr.cast::<&Vec<u8>>()) };
assert_eq!(ctx, &vec![1_u8, 2, 3, 4]);
})
}
@ -449,14 +586,14 @@ mod tests {
fn destructor(_val: u32, ctx: *mut c_void) {
assert!(!ctx.is_null());
let context = unsafe { *Box::from_raw(ctx as *mut Sender<bool>) };
let context = unsafe { *Box::from_raw(ctx.cast::<Sender<bool>>()) };
context.send(true).unwrap();
}
Python::with_gil(|py| {
let name = CString::new("foo").unwrap();
let cap = PyCapsule::new_with_destructor(py, 0, Some(name), destructor).unwrap();
cap.set_context(Box::into_raw(Box::new(tx)) as _).unwrap();
let cap = PyCapsule::new_bound_with_destructor(py, 0, Some(name), destructor).unwrap();
cap.set_context(Box::into_raw(Box::new(tx)).cast()).unwrap();
});
// the destructor was called.
@ -466,7 +603,7 @@ mod tests {
#[test]
fn test_pycapsule_no_name() {
Python::with_gil(|py| {
let cap = PyCapsule::new(py, 0usize, None).unwrap();
let cap = PyCapsule::new_bound(py, 0usize, None).unwrap();
assert_eq!(unsafe { cap.reference::<usize>() }, &0usize);
assert_eq!(cap.name().unwrap(), None);

View File

@ -1,6 +1,7 @@
use crate::derive_utils::PyFunctionArguments;
use crate::methods::PyMethodDefDestructor;
use crate::prelude::*;
use crate::types::capsule::PyCapsuleMethods;
use crate::{
ffi,
impl_::pymethods::{self, PyMethodDef},
@ -80,7 +81,7 @@ impl PyCFunction {
);
let (def, def_destructor) = method_def.as_method_def()?;
let capsule = PyCapsule::new(
let capsule = PyCapsule::new_bound(
py,
ClosureDestructor::<F> {
closure,

View File

@ -277,7 +277,7 @@ pub(crate) mod any;
pub(crate) mod boolobject;
pub(crate) mod bytearray;
pub(crate) mod bytes;
mod capsule;
pub(crate) mod capsule;
#[cfg(not(Py_LIMITED_API))]
mod code;
mod complex;