Add python 3 support to PyRustTypeBuilder.

This commit is contained in:
Daniel Grunwald 2015-06-28 04:12:51 +02:00
parent 00492e700c
commit 76e38e1a54
6 changed files with 267 additions and 42 deletions

View File

@ -90,9 +90,7 @@ pub use python::{Python, PythonObject, PythonObjectWithCheckedDowncast, PythonOb
pub use pythonrun::{GILGuard, GILProtected, prepare_freethreaded_python};
pub use conversion::{FromPyObject, ToPyObject};
pub use objectprotocol::{ObjectProtocol};
#[cfg(feature="python27-sys")]
pub use rustobject::{PyRustType, PyRustObject};
#[cfg(feature="python27-sys")]
pub use rustobject::typebuilder::PyRustTypeBuilder;
use std::ptr;
@ -114,7 +112,6 @@ mod objects;
mod objectprotocol;
mod pythonrun;
mod function;
#[cfg(feature="python27-sys")]
mod rustobject;
/// Private re-exports for macros. Do not use.
@ -125,14 +122,13 @@ pub mod _detail {
pub use abort_on_panic::PanicGuard;
pub use err::from_owned_ptr_or_panic;
pub use function::py_fn_impl;
#[cfg(feature="python27-sys")]
pub use rustobject::method::{py_method_impl, py_class_method_impl};
/// assume_gil_acquired(), but the returned Python<'p> is bounded by the scope
/// of the referenced variable.
/// This is useful in macros to ensure that type inference doesn't set 'p == 'static.
#[inline]
pub unsafe fn bounded_assume_gil_acquired<T>(_bound: &T) -> super::Python {
pub unsafe fn bounded_assume_gil_acquired<'p, T>(_bound: &'p T) -> super::Python<'p> {
super::Python::assume_gil_acquired()
}
}

View File

@ -110,7 +110,6 @@ impl <'p> PyModule<'p> {
/// This is a convenience function that creates a new `PyRustTypeBuilder` and
/// sets `new_type.__module__` to this module's name.
/// The new type will be added to this module when `finish()` is called on the builder.
#[cfg(feature="python27-sys")]
pub fn add_type<T>(&self, name: &str) -> ::rustobject::typebuilder::PyRustTypeBuilder<'p, T>
where T: 'p + Send {
::rustobject::typebuilder::new_typebuilder_for_module(self, name)

View File

@ -265,6 +265,23 @@ impl <'p> ToPyObject<'p> for str {
}
}
/// Converts rust `String` to Python object:
/// ASCII-only strings are converted to Python `str` objects;
/// other strings are converted to Python `unicode` objects.
///
/// Note that `str::ObjectType` differs based on Python version:
/// In Python 2.7, it is `PyObject` (`object` is the common base class of `str` and `unicode`).
/// In Python 3.x, it is `PyUnicode`.
impl <'p> ToPyObject<'p> for String {
type ObjectType = <str as ToPyObject<'p>>::ObjectType;
#[inline]
fn to_py_object(&self, py: Python<'p>) -> Self::ObjectType {
<str as ToPyObject>::to_py_object(self, py)
}
}
/// Allows extracting strings from Python objects.
/// Accepts Python `str` and `unicode` objects.
/// In Python 2.7, `str` is expected to be UTF-8 encoded.

View File

@ -17,7 +17,7 @@
// DEALINGS IN THE SOFTWARE.
use std::marker;
use python::PythonObject;
use python::{Python, PythonObject};
use objects::{PyObject, PyTuple, PyType};
use super::typebuilder::TypeMember;
use ffi;
@ -108,12 +108,17 @@ pub unsafe fn py_method_impl<'p, T, R>(
impl <'p, T> TypeMember<'p, T> for MethodDescriptor<T> where T: PythonObject<'p> {
#[inline]
fn into_descriptor(self, ty: &PyType<'p>, _name: &str) -> PyObject<'p> {
fn to_descriptor(&self, ty: &PyType<'p>, _name: &str) -> PyObject<'p> {
unsafe {
err::from_owned_ptr_or_panic(ty.python(),
ffi::PyDescr_NewMethod(ty.as_type_ptr(), self.0))
}
}
#[inline]
fn into_box(self, _py: Python<'p>) -> Box<TypeMember<'p, T> + 'p> {
Box::new(self)
}
}
@ -198,11 +203,16 @@ pub unsafe fn py_class_method_impl<'p, R>(
impl <'p, T> TypeMember<'p, T> for ClassMethodDescriptor where T: PythonObject<'p> {
#[inline]
fn into_descriptor(self, ty: &PyType<'p>, _name: &str) -> PyObject<'p> {
fn to_descriptor(&self, ty: &PyType<'p>, _name: &str) -> PyObject<'p> {
unsafe {
err::from_owned_ptr_or_panic(ty.python(),
ffi::PyDescr_NewClassMethod(ty.as_type_ptr(), self.0))
}
}
#[inline]
fn into_box(self, _py: Python<'p>) -> Box<TypeMember<'p, T> + 'p> {
Box::new(self)
}
}

View File

@ -58,17 +58,22 @@ impl <'p> PythonBaseObject<'p> for PyObject<'p> {
unsafe fn alloc(ty: &PyType<'p>, _init_val: ()) -> PyResult<'p, PyObject<'p>> {
let py = ty.python();
let ptr = ((*ty.as_type_ptr()).tp_alloc.unwrap())(ty.as_type_ptr(), 0);
let ptr = ffi::PyType_GenericAlloc(ty.as_type_ptr(), 0);
err::result_from_owned_ptr(py, ptr)
}
unsafe fn dealloc(ptr: *mut ffi::PyObject) {
// Unfortunately, there is no PyType_GenericFree, so
// we have to manually un-do the work of PyType_GenericAlloc:
let ty = ffi::Py_TYPE(ptr);
((*ty).tp_free.unwrap())(ptr as *mut libc::c_void);
// For heap types, tp_alloc calls INCREF on the type objects,
// but tp_free points directly to the memory deallocator and does not call DECREF.
// So we'll do that manually here:
if ((*ty).tp_flags & ffi::Py_TPFLAGS_HEAPTYPE) != 0 {
if ffi::PyType_IS_GC(ty) != 0 {
ffi::PyObject_GC_Del(ptr as *mut libc::c_void);
} else {
ffi::PyObject_Free(ptr as *mut libc::c_void);
}
// For heap types, PyType_GenericAlloc calls INCREF on the type objects,
// so we need to call DECREF here:
if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 {
ffi::Py_DECREF(ty as *mut ffi::PyObject);
}
}

View File

@ -17,29 +17,60 @@
// DEALINGS IN THE SOFTWARE.
use std::{ptr, marker};
use std::ffi::{CStr, CString};
use libc;
use ffi;
use python::{Python, ToPythonPointer, PythonObject};
use conversion::ToPyObject;
use objects::{PyObject, PyType, PyString, PyModule, PyDict};
use err::{self, PyResult};
use objectprotocol::ObjectProtocol;
use super::{PythonBaseObject, PyRustObject, PyRustType};
#[repr(C)]
#[must_use]
pub struct PyRustTypeBuilder<'p, T, B = PyObject<'p>> where T: 'static + Send, B: PythonBaseObject<'p> {
// In Python 2.7, we can create a new PyHeapTypeObject and fill it.
/// The python type object under construction.
#[cfg(feature="python27-sys")]
type_obj: PyType<'p>,
target_module: Option<PyModule<'p>>,
/// The full PyHeapTypeObject under construction.
#[cfg(feature="python27-sys")]
ht: *mut ffi::PyHeapTypeObject,
// In Python 3.x with PEP 384, we prepare the relevant
// information and then create the type in `finish()`.
/// Name of the type to be created
#[cfg(feature="python3-sys")]
name: CString,
/// Flags of the type to be created
#[cfg(feature="python3-sys")]
flags: libc::c_uint,
/// Slots to use when creating the type
#[cfg(feature="python3-sys")]
slots: Vec<ffi::PyType_Slot>,
/// Maintains owned reference for base type object
#[cfg(feature="python3-sys")]
tp_base: Option<PyType<'p>>,
/// List of future type members
#[cfg(feature="python3-sys")]
members: Vec<(String, Box<TypeMember<'p, PyRustObject<'p, T, B>> + 'p>)>,
/// The documentation string.
doc_str: Option<CString>,
/// The module to which the new type should be added.
target_module: Option<PyModule<'p>>,
/// Whether PyTypeBuilder::base() might be called
can_change_base: bool,
py: Python<'p>,
phantom: marker::PhantomData<&'p (B, T)>
}
pub fn new_typebuilder_for_module<'p, T>(m: &PyModule<'p>, name: &str) -> PyRustTypeBuilder<'p, T>
where T: 'static + Send {
let b = PyRustTypeBuilder::new(m.python(), name);
if let Ok(mod_name) = m.name() {
b.dict().set_item("__module__", mod_name).ok();
}
PyRustTypeBuilder { target_module: Some(m.clone()), .. b }
}
@ -63,6 +94,7 @@ impl <'p, T> PyRustTypeBuilder<'p, T> where T: 'static + Send {
///
/// py: proof that the GIL is held by the current thread.
/// name: name of the new type
#[cfg(feature="python27-sys")]
pub fn new(py: Python<'p>, name: &str) -> PyRustTypeBuilder<'p, T> {
unsafe {
let obj = (ffi::PyType_Type.tp_alloc.unwrap())(&mut ffi::PyType_Type, 0);
@ -71,24 +103,51 @@ impl <'p, T> PyRustTypeBuilder<'p, T> where T: 'static + Send {
}
debug_assert!(ffi::Py_REFCNT(obj) == 1);
let ht = obj as *mut ffi::PyHeapTypeObject;
// flags must be set first, before the GC traverses the object
(*ht).ht_type.tp_flags = ffi::Py_TPFLAGS_DEFAULT | ffi::Py_TPFLAGS_HEAPTYPE;
(*ht).ht_name = PyString::new(py, name.as_bytes()).steal_ptr();
(*ht).ht_type.tp_name = ffi::PyString_AS_STRING((*ht).ht_name);
(*ht).ht_type.tp_flags = ffi::Py_TPFLAGS_DEFAULT | ffi::Py_TPFLAGS_HEAPTYPE;
(*ht).ht_type.tp_new = Some(disabled_tp_new_callback);
PyRustTypeBuilder {
type_obj: PyType::unchecked_downcast_from(PyObject::from_owned_ptr(py, obj)),
doc_str: None,
target_module: None,
ht: ht,
can_change_base: true,
py: py,
phantom: marker::PhantomData
}
}
}
/// Create a new type builder.
///
/// py: proof that the GIL is held by the current thread.
/// name: name of the new type
#[cfg(feature="python3-sys")]
pub fn new(py: Python<'p>, name: &str) -> PyRustTypeBuilder<'p, T> {
PyRustTypeBuilder {
name: CString::new(name).unwrap(),
flags: ffi::Py_TPFLAGS_DEFAULT as libc::c_uint,
slots: Vec::new(),
tp_base: None,
members: Vec::new(),
target_module: None,
doc_str: None,
can_change_base: true,
py: py,
phantom: marker::PhantomData
}
}
/// Sets the base class that this type is inheriting from.
#[cfg(feature="python27-sys")]
pub fn base<T2, B2>(self, base_type: &PyRustType<'p, T2, B2>)
-> PyRustTypeBuilder<'p, T, PyRustObject<'p, T2, B2>>
where T2: 'static + Send, B2: PythonBaseObject<'p>
{
assert!(self.can_change_base,
"base() must be called before any members are added to the type");
unsafe {
ffi::Py_XDECREF((*self.ht).ht_type.tp_base as *mut ffi::PyObject);
(*self.ht).ht_type.tp_base = base_type.as_type_ptr();
@ -96,60 +155,93 @@ impl <'p, T> PyRustTypeBuilder<'p, T> where T: 'static + Send {
}
PyRustTypeBuilder {
type_obj: self.type_obj,
doc_str: self.doc_str,
target_module: self.target_module,
ht: self.ht,
can_change_base: false,
py: self.py,
phantom: marker::PhantomData
}
}
/// Sets the base class that this type is inheriting from.
#[cfg(feature="python3-sys")]
pub fn base<T2, B2>(self, base_type: &PyRustType<'p, T2, B2>)
-> PyRustTypeBuilder<'p, T, PyRustObject<'p, T2, B2>>
where T2: 'static + Send, B2: PythonBaseObject<'p>
{
// Ensure we can't change the base after any callbacks are registered.
assert!(self.can_change_base && self.members.len() == 0,
"base() must be called before any members are added to the type");
let base_type_obj: &PyType = base_type;
PyRustTypeBuilder {
name: self.name,
flags: self.flags,
slots: self.slots,
tp_base: Some(base_type_obj.clone()),
members: Vec::new(),
target_module: self.target_module,
doc_str: self.doc_str,
can_change_base: false,
py: self.py,
phantom: marker::PhantomData
}
}
}
impl <'p, T, B> PyRustTypeBuilder<'p, T, B> where T: 'static + Send, B: PythonBaseObject<'p> {
/// Retrieves the type dictionary of the type being built.
pub fn dict(&self) -> PyDict<'p> {
let py = self.type_obj.python();
#[cfg(feature="python27-sys")]
fn dict(&self) -> PyDict<'p> {
unsafe {
if (*self.ht).ht_type.tp_dict.is_null() {
(*self.ht).ht_type.tp_dict = PyDict::new(py).steal_ptr();
(*self.ht).ht_type.tp_dict = PyDict::new(self.py).steal_ptr();
}
PyDict::unchecked_downcast_from(PyObject::from_borrowed_ptr(py, (*self.ht).ht_type.tp_dict))
PyDict::unchecked_downcast_from(PyObject::from_borrowed_ptr(self.py, (*self.ht).ht_type.tp_dict))
}
}
/// Set the doc string on the type being built.
pub fn doc(self, doc_str: &str) -> Self {
unsafe {
if !(*self.ht).ht_type.tp_doc.is_null() {
ffi::PyObject_Free((*self.ht).ht_type.tp_doc as *mut libc::c_void);
}
// ht_type.tp_doc must be allocated with PyObject_Malloc
let p = ffi::PyObject_Malloc((doc_str.len() + 1) as libc::size_t);
(*self.ht).ht_type.tp_doc = p as *const libc::c_char;
if p.is_null() {
panic!("Out of memory")
}
ptr::copy_nonoverlapping(doc_str.as_ptr(), p as *mut u8, doc_str.len() + 1);
PyRustTypeBuilder { doc_str: Some(CString::new(doc_str).unwrap()), .. self }
}
/// Adds a new member to the type.
#[cfg(feature="python27-sys")]
pub fn add<M>(mut self, name: &str, val: M) -> Self
where M: TypeMember<'p, PyRustObject<'p, T, B>> {
self.can_change_base = false;
self.dict().set_item(name, val.to_descriptor(&self.type_obj, name)).unwrap();
self
}
/// Adds a new member to the type.
pub fn add<M>(self, name: &str, val: M) -> Self
#[cfg(feature="python3-sys")]
pub fn add<M>(mut self, name: &str, val: M) -> Self
where M: TypeMember<'p, PyRustObject<'p, T, B>> {
self.dict().set_item(name, val.into_descriptor(&self.type_obj, name)).unwrap();
self.can_change_base = false;
self.members.push((name.to_owned(), val.into_box(self.py)));
self
}
/// Finalize construction of the new type.
#[cfg(feature="python27-sys")]
pub fn finish(self) -> PyResult<'p, PyRustType<'p, T, B>> {
let py = self.type_obj.python();
let py = self.py;
unsafe {
(*self.ht).ht_type.tp_basicsize = PyRustObject::<T, B>::size() as ffi::Py_ssize_t;
(*self.ht).ht_type.tp_dealloc = Some(tp_dealloc_callback::<T, B>);
if let Some(s) = self.doc_str {
(*self.ht).ht_type.tp_doc = copy_str_to_py_malloc_heap(&s);
}
try!(err::error_on_minusone(py, ffi::PyType_Ready(self.type_obj.as_type_ptr())))
}
if let Some(m) = self.target_module {
// Set module name for new type
if let Ok(mod_name) = m.name() {
try!(self.type_obj.as_object().setattr("__module__", mod_name));
}
// Register the new type in the target module
let name = unsafe { PyObject::from_borrowed_ptr(py, (*self.ht).ht_name) };
try!(m.dict().set_item(name, self.type_obj.as_object()));
@ -160,21 +252,127 @@ impl <'p, T, B> PyRustTypeBuilder<'p, T, B> where T: 'static + Send, B: PythonBa
})
}
/// Finalize construction of the new type.
#[cfg(feature="python3-sys")]
pub fn finish(mut self) -> PyResult<'p, PyRustType<'p, T, B>> {
// push some more slots
self.slots.push(ffi::PyType_Slot {
slot: ffi::Py_tp_dealloc,
pfunc: tp_dealloc_callback::<T, B> as ffi::destructor as *mut libc::c_void
});
if let Some(s) = self.doc_str {
self.slots.push(ffi::PyType_Slot {
slot: ffi::Py_tp_doc,
pfunc: copy_str_to_py_malloc_heap(&s) as *mut libc::c_void
});
}
if let Some(base_type) = self.tp_base {
self.slots.push(ffi::PyType_Slot {
slot: ffi::Py_tp_base,
pfunc: base_type.as_type_ptr() as *mut libc::c_void
});
}
let type_obj = try!(unsafe { create_type_from_slots(
self.py, &self.name, PyRustObject::<T, B>::size(),
self.flags, &mut self.slots) });
for (name, member) in self.members {
let descr = member.to_descriptor(&type_obj, &name);
try!(type_obj.as_object().setattr(name, descr));
}
if let Some(m) = self.target_module {
// Set module name for new type
if let Ok(mod_name) = m.name() {
try!(type_obj.as_object().setattr("__module__", mod_name));
}
// Register the new type in the target module
unsafe {
try!(err::error_on_minusone(self.py,
ffi::PyDict_SetItemString(
m.dict().as_object().as_ptr(),
self.name.as_ptr(),
type_obj.as_object().as_ptr())
));
}
}
Ok(PyRustType {
type_obj: type_obj,
phantom: marker::PhantomData
})
}
}
fn copy_str_to_py_malloc_heap(s: &CStr) -> *mut libc::c_char {
copy_to_py_malloc_heap(s.to_bytes_with_nul()) as *mut libc::c_char
}
fn copy_to_py_malloc_heap(s: &[u8]) -> *mut u8 {
unsafe {
let p = ffi::PyObject_Malloc(s.len() as libc::size_t) as *mut u8;
if p.is_null() {
panic!("Out of memory")
}
ptr::copy_nonoverlapping(s.as_ptr(), p, s.len());
p
}
}
#[cfg(feature="python3-sys")]
unsafe fn create_type_from_slots<'p>(
py: Python<'p>,
name: &CStr,
basicsize: usize,
flags: libc::c_uint,
slots: &mut Vec<ffi::PyType_Slot>
) -> PyResult<'p, PyType<'p>>
{
// ensure the necessary slots are set:
if !slots.iter().any(|s| s.slot == ffi::Py_tp_new) {
slots.push(ffi::PyType_Slot {
slot: ffi::Py_tp_new,
pfunc: disabled_tp_new_callback as ffi::newfunc as *mut libc::c_void
});
}
slots.push(ffi::PyType_Slot::default()); // sentinel
let mut spec = ffi::PyType_Spec {
name: name.as_ptr(),
basicsize: basicsize as libc::c_int,
itemsize: 0,
flags: flags,
slots: slots.as_mut_ptr()
};
err::result_cast_from_owned_ptr(py,
ffi::PyType_FromSpec(&mut spec))
}
/// Represents something that can be added as a member to a Python class/type.
///
/// T: type of rust class used for instances of the Python class/type.
pub trait TypeMember<'p, T> where T: PythonObject<'p> {
fn into_descriptor(self, ty: &PyType<'p>, name: &str) -> PyObject<'p>;
/// Convert the type member into a python object
/// that can be stored in the type dict.
fn to_descriptor(&self, ty: &PyType<'p>, name: &str) -> PyObject<'p>;
/// Put the type member into a box with lifetime `'p` so that
/// it can be used at a later point in time.
///
/// `PyRustTypeBuilder:add()` may use this function to store the member,
/// with `into_descriptor()` being called from the `finish()` method.
fn into_box(self, py: Python<'p>) -> Box<TypeMember<'p, T> + 'p>;
}
// TODO: does this cause trouble for coherence?
impl <'p, T, S> TypeMember<'p, T> for S where T: PythonObject<'p>, S: ToPyObject<'p> {
#[inline]
fn into_descriptor(self, ty: &PyType<'p>, _name: &str) -> PyObject<'p> {
self.into_py_object(ty.python()).into_object()
fn to_descriptor(&self, ty: &PyType<'p>, _name: &str) -> PyObject<'p> {
self.to_py_object(ty.python()).into_object()
}
#[inline]
fn into_box(self, py: Python<'p>) -> Box<TypeMember<'p, T> + 'p> {
Box::new(self.into_py_object(py).into_object())
}
}