Skip to content

WIP: Add support for the buffer protocol #227

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions python3-sys/src/memoryobject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ extern "C" {
size: Py_ssize_t,
flags: c_int,
) -> *mut PyObject;
pub fn PyMemoryView_FromBuffer(view: *mut Py_buffer) -> *mut PyObject;
pub fn PyMemoryView_GetContiguous(
base: *mut PyObject,
buffertype: c_int,
Expand Down
8 changes: 6 additions & 2 deletions python3-sys/src/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,13 @@ mod bufferinfo {
}
}

pub type getbufferproc = extern "C" fn(
pub type getbufferproc = unsafe extern "C" fn(
arg1: *mut crate::object::PyObject,
arg2: *mut Py_buffer,
arg3: c_int,
) -> c_int;
pub type releasebufferproc =
extern "C" fn(arg1: *mut crate::object::PyObject, arg2: *mut Py_buffer) -> ();
unsafe extern "C" fn(arg1: *mut crate::object::PyObject, arg2: *mut Py_buffer) -> ();

/// Maximum number of dimensions
pub const PyBUF_MAX_NDIM: c_int = 64;
Expand Down Expand Up @@ -430,6 +430,10 @@ mod typeobject {
unsafe { core::mem::zeroed() }
}
}
pub const PyBufferProcs_INIT: PyBufferProcs = PyBufferProcs {
bf_getbuffer: None,
bf_releasebuffer: None,
};

#[repr(C)]
#[derive(Copy)]
Expand Down
32 changes: 31 additions & 1 deletion src/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

use libc;
use std::ffi::CStr;
use std::{cell, mem, slice};
use std::{cell, mem, ptr, slice};

use crate::err::{self, PyResult};
use crate::exc;
Expand Down Expand Up @@ -661,6 +661,36 @@ impl_element!(isize, SignedInteger);
impl_element!(f32, Float);
impl_element!(f64, Float);

/// Trait for the backing storage of a PyBuffer provided from a Rust binding.
///
/// It is unsafe because its implementation needs to uphold certain invariants.
/// See the method docs for more details.
pub unsafe trait BufferHandle: 'static + Send {
/// Returns the data of `Self` as a continous array of bytes.
///
/// # Safety
///
/// This needs to return an address that remains valid
/// and stable if `self` gets moved or transformed to or from a void pointer.
fn as_bytes(&self) -> &[u8];

/// Convert `self` into an owned void pointer.
///
/// # Safety
///
/// The returned pointer has to be a valid pointer that
/// can be converted back to `Self`.
fn into_owned_void_pointer(self) -> *mut libc::c_void;

/// Convert an owned void pointer back to `Self`. This takes owenrship of the pointer.
///
/// # Safety
///
/// The passed `ptr` has been created by this trait, and is only
/// used at most once to convert back to `Self`.
unsafe fn from_owned_void_pointer(ptr: *mut libc::c_void) -> Self;
}

#[cfg(test)]
mod test {
use super::PyBuffer;
Expand Down
8 changes: 8 additions & 0 deletions src/py_class/py_class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,10 @@ macro_rules! py_class {
/* as_number */ [ /* slot: expr, */ ]
/* as_sequence */ [ /* slot: expr, */ ]
/* as_mapping */ [ /* slot: expr, */ ]
/* as_buffer */ [
bf_getbuffer: {},
bf_releasebuffer: {},
]
/* setitem_delitem */ [
sdi_setitem: {},
sdi_delitem: {},
Expand Down Expand Up @@ -495,6 +499,10 @@ macro_rules! py_class {
/* as_number */ [ /* slot: expr, */ ]
/* as_sequence */ [ /* slot: expr, */ ]
/* as_mapping */ [ /* slot: expr, */ ]
/* as_buffer */ [
bf_getbuffer: {},
bf_releasebuffer: {},
]
/* setitem_delitem */ [
sdi_setitem: {},
sdi_delitem: {},
Expand Down
32 changes: 30 additions & 2 deletions src/py_class/py_class_impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,8 @@ def write(text):
('nb', 'as_number', None),
('sq', 'as_sequence', None),
('mp', 'as_mapping', None),
('sdi', 'setdelitem', ['sdi_setitem', 'sdi_delitem'])
('bf', 'as_buffer', ['bf_getbuffer', 'bf_releasebuffer']),
('sdi', 'setdelitem', ['sdi_setitem', 'sdi_delitem']),
)

def generate_case(pattern, old_info=None, new_info=None, new_impl=None, new_slots=None, new_members=None, new_props=None):
Expand Down Expand Up @@ -723,6 +724,30 @@ def inplace_numeric_operator(special_name, slot):
operator(slot=slot,
args=[Argument('other')])(special_name)

@special_method
def buffer_protocol(special_name, enabled, mode):
if enabled:
if mode == "direct":
pattern = 'def %s <$gil_lt:lifetime>(&$slf_lt:lifetime $slf:ident) -> $res_type:ty $body:block' % special_name
new_impl='''
impl $class {
fn %s<$gil_lt>(&$slf_lt $slf, $py: $crate::Python<$gil_lt>) -> $res_type $body
}
''' % special_name

if mode == "handle":
pattern = 'def %s (&$slf:ident) -> $res_type:ty $body:block' % special_name
new_impl='''
impl $class {
fn %s(&$slf, $py: $crate::Python<'_>) -> $res_type $body
}
''' % special_name

new_slots = []
for slot in ['bf_getbuffer', 'bf_releasebuffer']:
new_slots.append((slot, '$crate::py_class_buffer_slot!(%s, %s, $class::%s)' % (mode, slot, special_name)))
generate_case(pattern, new_slots=new_slots, new_impl=new_impl)

special_names = {
'__init__': error('__init__ is not supported by py_class!; use __new__ instead.'),
'__new__': special_class_method(
Expand Down Expand Up @@ -870,6 +895,10 @@ def inplace_numeric_operator(special_name, slot):
'__aiter__': unimplemented(),
'__aenter__': unimplemented(),
'__aexit__': unimplemented(),

# Buffer protocol
'__buffer__': buffer_protocol(not PY2, "handle"),
'__direct_buffer__': buffer_protocol(not PY2, "direct"),
}

def main():
Expand Down Expand Up @@ -907,4 +936,3 @@ def main():

if __name__ == '__main__':
main()

Loading