Macro cpython::py_capsule

source ·
macro_rules! py_capsule {
    (from $($capsmod:ident).+ import $capsname:ident as $rustmod:ident for $ruststruct: ident ) => { ... };
}
Expand description

Macro to retrieve a Python capsule pointing to an array of data, with a layer of caching.

For more details on capsules, see PyCapsule

The caller has to define an appropriate repr(C) struct first, and put it in scope (use) if needed along the macro invocation.

§Usage

  py_capsule!(from some.python.module import capsulename as rustmodule for CapsuleStruct)

where CapsuleStruct is the above mentioned struct defined by the caller.

The macro defines a Rust module named rustmodule, as specified by the caller. This module provides a retrieval function with the following signature:

mod rustmodule {
    pub unsafe fn retrieve<'a>(py: Python) -> PyResult<&'a CapsuleStruct> { ... }
}

The retrieve() function is unsafe for the same reasons as PyCapsule::import_data, upon which it relies.

The newly defined module also contains a RawPyObject type suitable to represent C-level Python objects. It can be used in cpython public API involving raw FFI pointers, such as [from_owned_ptr].

§Examples

§Using a capsule from the standard library

This retrieves and uses one of the simplest capsules in the Python standard library, found in the unicodedata module. The C API enclosed in this capsule is the same for all Python versions supported by this crate.

In this case, as with all capsules from the Python standard library, the capsule data is an array (static struct) with constants and function pointers.

use cpython::{Python, PyCapsule, py_capsule};
use libc::{c_char, c_int};
use std::ffi::{c_void, CStr, CString};
use std::mem;
use std::ptr::null;

#[allow(non_camel_case_types)]
type Py_UCS4 = u32;
const UNICODE_NAME_MAXLEN: usize = 256;

#[repr(C)]
pub struct unicode_name_CAPI {
    // the `ucd` signature arguments are actually optional (can be `NULL`) FFI PyObject
    // pointers used to pass alternate (former) versions of Unicode data.
    // We won't need to use them with an actual value in these examples, so it's enough to
    // specify them as `const c_void`, and it spares us a direct reference to the lower
    // level Python FFI bindings.
    size: c_int,
    getname: unsafe extern "C" fn(
        ucd: *const c_void,
        code: Py_UCS4,
        buffer: *const c_char,
        buflen: c_int,
        with_alias_and_seq: c_int,
    ) -> c_int,
    getcode: unsafe extern "C" fn(
        ucd: *const c_void,
        name: *const c_char,
        namelen: c_int,
        code: *const Py_UCS4,
    ) -> c_int,
}

#[derive(Debug, PartialEq)]
pub enum UnicodeDataError {
    InvalidCode,
    UnknownName,
}

impl unicode_name_CAPI {
    pub fn get_name(&self, code: Py_UCS4) -> Result<CString, UnicodeDataError> {
        let mut buf: Vec<c_char> = Vec::with_capacity(UNICODE_NAME_MAXLEN);
        let buf_ptr = buf.as_mut_ptr();
        if unsafe {
          ((*self).getname)(null(), code, buf_ptr, UNICODE_NAME_MAXLEN as c_int, 0)
        } != 1 {
            return Err(UnicodeDataError::InvalidCode);
        }
        mem::forget(buf);
        Ok(unsafe { CString::from_raw(buf_ptr) })
    }

    pub fn get_code(&self, name: &CStr) -> Result<Py_UCS4, UnicodeDataError> {
        let namelen = name.to_bytes().len() as c_int;
        let mut code: [Py_UCS4; 1] = [0; 1];
        if unsafe {
            ((*self).getcode)(null(), name.as_ptr(), namelen, code.as_mut_ptr())
        } != 1 {
            return Err(UnicodeDataError::UnknownName);
        }
        Ok(code[0])
    }
}

py_capsule!(from unicodedata import ucnhash_CAPI as capsmod for unicode_name_CAPI);

fn main() {
    let gil = Python::acquire_gil();
    let py = gil.python();

    let capi = unsafe { capsmod::retrieve(py).unwrap() };
    assert_eq!(capi.get_name(32).unwrap().to_str(), Ok("SPACE"));
    assert_eq!(capi.get_name(0), Err(UnicodeDataError::InvalidCode));

    assert_eq!(capi.get_code(CStr::from_bytes_with_nul(b"COMMA\0").unwrap()), Ok(44));
    assert_eq!(capi.get_code(CStr::from_bytes_with_nul(b"\0").unwrap()),
               Err(UnicodeDataError::UnknownName));
}

§With Python objects

In this example, we lend a Python object and receive a new one of which we take ownership.

use cpython::{PyCapsule, PyObject, PyResult, Python, py_capsule};
use libc::c_void;

// In the struct, we still have to use c_void for C-level Python objects.
#[repr(C)]
pub struct spawn_CAPI {
    spawnfrom: unsafe extern "C" fn(obj: *const c_void) -> *mut c_void,
}

py_capsule!(from some.mod import CAPI as capsmod for spawn_CAPI);

impl spawn_CAPI {
   pub fn spawn_from(&self, py: Python, obj: PyObject) -> PyResult<PyObject> {
       let raw = obj.as_ptr() as *const c_void;
       Ok(unsafe {
           PyObject::from_owned_ptr(
               py,
               ((*self).spawnfrom)(raw) as *mut capsmod::RawPyObject)
       })
   }
}