Skip to content

Commit

Permalink
wip: pycapsule support
Browse files Browse the repository at this point in the history
  • Loading branch information
kylebarron committed Nov 13, 2023
1 parent 6521cba commit 92b070a
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 5 deletions.
8 changes: 6 additions & 2 deletions arrow-data/src/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,12 @@ impl Drop for FFI_ArrowArray {
unsafe impl Send for FFI_ArrowArray {}
unsafe impl Sync for FFI_ArrowArray {}

// callback used to drop [FFI_ArrowArray] when it is exported
unsafe extern "C" fn release_array(array: *mut FFI_ArrowArray) {
/// callback used to drop [FFI_ArrowArray] when it is exported
///
/// # Safety
///
/// Must be passed a valid [FFI_ArrowArray].
pub unsafe extern "C" fn release_array(array: *mut FFI_ArrowArray) {
if array.is_null() {
return;
}
Expand Down
11 changes: 9 additions & 2 deletions arrow-schema/src/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,12 @@ struct SchemaPrivateData {
metadata: Option<Vec<u8>>,
}

// callback used to drop [FFI_ArrowSchema] when it is exported.
unsafe extern "C" fn release_schema(schema: *mut FFI_ArrowSchema) {
/// callback used to drop [FFI_ArrowSchema] when it is exported.
///
/// # Safety
///
/// Must be passed a valid [FFI_ArrowSchema].
pub unsafe extern "C" fn release_schema(schema: *mut FFI_ArrowSchema) {
if schema.is_null() {
return;
}
Expand Down Expand Up @@ -351,6 +355,9 @@ impl Drop for FFI_ArrowSchema {
}
}

unsafe impl Send for FFI_ArrowSchema {}
unsafe impl Sync for FFI_ArrowSchema {}

impl TryFrom<&FFI_ArrowSchema> for DataType {
type Error = ArrowError;

Expand Down
80 changes: 79 additions & 1 deletion arrow/src/pyarrow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ use pyo3::exceptions::{PyTypeError, PyValueError};
use pyo3::ffi::Py_uintptr_t;
use pyo3::import_exception;
use pyo3::prelude::*;
use pyo3::types::{PyList, PyTuple};
use pyo3::types::{PyCapsule, PyList, PyTuple};

use crate::array::{make_array, ArrayData};
use crate::datatypes::{DataType, Field, Schema};
Expand Down Expand Up @@ -118,8 +118,39 @@ fn validate_class(expected: &str, value: &PyAny) -> PyResult<()> {
Ok(())
}

fn validate_pycapsule(capsule: &PyCapsule, name: &str) -> PyResult<()> {
let capsule_name = capsule.name()?;
if capsule_name.is_none() {
return Err(PyValueError::new_err(
"Expected schema PyCapsule to have name set.",
));
}

let capsule_name = capsule_name.unwrap().to_str()?;
if capsule_name != name {
return Err(PyValueError::new_err(format!(
"Expected name '{}' in PyCapsule.",
name,
)));
}

Ok(())
}

impl FromPyArrow for DataType {
fn from_pyarrow(value: &PyAny) -> PyResult<Self> {
// Newer versions of PyArrow as well as other libraries with Arrow data implement this
// method, so prefer it over _export_to_c.
if value.hasattr("__arrow_c_schema__")? {
let capsule: &PyCapsule =
PyTryInto::try_into(value.getattr("__arrow_c_schema__")?.call0()?)?;
validate_pycapsule(capsule, "arrow_schema")?;

let schema_ptr = unsafe { capsule.reference::<FFI_ArrowSchema>() };
let dtype = DataType::try_from(schema_ptr).map_err(to_py_err)?;
Ok(dtype)
}

validate_class("DataType", value)?;

let c_schema = FFI_ArrowSchema::empty();
Expand All @@ -143,6 +174,18 @@ impl ToPyArrow for DataType {

impl FromPyArrow for Field {
fn from_pyarrow(value: &PyAny) -> PyResult<Self> {
// Newer versions of PyArrow as well as other libraries with Arrow data implement this
// method, so prefer it over _export_to_c.
if value.hasattr("__arrow_c_schema__")? {
let capsule: &PyCapsule =
PyTryInto::try_into(value.getattr("__arrow_c_schema__")?.call0()?)?;
validate_pycapsule(capsule, "arrow_schema")?;

let schema_ptr = unsafe { capsule.reference::<FFI_ArrowSchema>() };
let field = Field::try_from(schema_ptr).map_err(to_py_err)?;
Ok(field)
}

validate_class("Field", value)?;

let c_schema = FFI_ArrowSchema::empty();
Expand All @@ -166,6 +209,18 @@ impl ToPyArrow for Field {

impl FromPyArrow for Schema {
fn from_pyarrow(value: &PyAny) -> PyResult<Self> {
// Newer versions of PyArrow as well as other libraries with Arrow data implement this
// method, so prefer it over _export_to_c.
if value.hasattr("__arrow_c_schema__")? {
let capsule: &PyCapsule =
PyTryInto::try_into(value.getattr("__arrow_c_schema__")?.call0()?)?;
validate_pycapsule(capsule, "arrow_schema")?;

let schema_ptr = unsafe { capsule.reference::<FFI_ArrowSchema>() };
let schema = Schema::try_from(&c_schema).map_err(to_py_err)?;
Ok(schema)
}

validate_class("Schema", value)?;

let c_schema = FFI_ArrowSchema::empty();
Expand All @@ -189,6 +244,29 @@ impl ToPyArrow for Schema {

impl FromPyArrow for ArrayData {
fn from_pyarrow(value: &PyAny) -> PyResult<Self> {
// Newer versions of PyArrow as well as other libraries with Arrow data implement this
// method, so prefer it over _export_to_c.
if value.hasattr("__arrow_c_array__")? {
let tuple = value.getattr("__arrow_c_array__")?.call0()?;

if !tuple.is_instance_of::<PyTuple>() {
return Err(PyTypeError::new_err(
"Expected __arrow_c_array__ to return a tuple.",
));
}

let schema_capsule: &PyCapsule = PyTryInto::try_into(tuple.get_item(0)?)?;
let array_capsule: &PyCapsule = PyTryInto::try_into(tuple.get_item(1)?)?;

validate_pycapsule(schema_capsule, "arrow_schema")?;
validate_pycapsule(array_capsule, "arrow_array")?;

let schema_ptr = unsafe { schema_capsule.reference::<FFI_ArrowSchema>() };
let array_ptr = unsafe { array_capsule.reference::<FFI_ArrowArray>() };

ffi::from_ffi(array_ptr.copy(), schema_ptr).map_err(to_py_err)
}

validate_class("Array", value)?;

// prepare a pointer to receive the Array struct
Expand Down

0 comments on commit 92b070a

Please sign in to comment.