Add py_method!() macro for creating method descriptors.

This commit is contained in:
Daniel Grunwald 2015-06-25 23:58:57 +02:00
parent 5c74f55862
commit 4980053e95
8 changed files with 273 additions and 69 deletions

View file

@ -33,6 +33,8 @@ stamps/test-hello: hello.out
@grep "Rust says: Hello Python!" hello.out >/dev/null
@grep "Rust got 42" hello.out >/dev/null
all: custom_type.so
all: inheritance.so

21
extensions/custom_type.rs Normal file
View file

@ -0,0 +1,21 @@
#![crate_type = "dylib"]
#![feature(plugin)]
#![plugin(interpolate_idents)]
#[macro_use] extern crate cpython;
use cpython::{PythonObject, PyObject, PyRustObject, PyTuple, PyResult};
py_module_initializer!(custom_type, |_py, m| {
try!(m.add("__doc__", "Module documentation string"));
try!(m.add_type::<i32>("MyType")
.add("a", py_method!(a))
.finish());
Ok(())
});
fn a<'p>(slf: &PyRustObject<'p, i32>, args: &PyTuple<'p>) -> PyResult<'p, PyObject<'p>> {
println!("a() was called with self={:?} and args={:?}", slf.get(), args.as_object());
Ok(slf.python().None())
}

87
src/function.rs Normal file
View file

@ -0,0 +1,87 @@
// Copyright (c) 2015 Daniel Grunwald
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
use libc::c_char;
use std::ptr;
use python::Python;
use objects::PyObject;
use conversion::ToPyObject;
use ffi;
use err;
/// Creates a python callable object that invokes a Rust function.
///
/// As arguments, takes the name of a rust function with the signature
/// `for<'p> fn(Python<'p>, &PyTuple<'p>) -> PyResult<'p, T>`
/// for some `T` that implements `ToPyObject`.
///
/// Returns a type that implements `ToPyObject` by producing a python callable.
///
/// See `py_module_initializer!` for example usage.
#[macro_export]
macro_rules! py_fn {
($f: ident) => ( interpolate_idents! {{
unsafe extern "C" fn [ wrap_ $f ](
_slf: *mut $crate::_detail::ffi::PyObject,
args: *mut $crate::_detail::ffi::PyObject)
-> *mut $crate::_detail::ffi::PyObject
{
let _guard = $crate::_detail::PanicGuard::with_message("Rust panic in py_fn!");
let py = $crate::_detail::bounded_assume_gil_acquired(&args);
let args = $crate::PyObject::from_borrowed_ptr(py, args);
let args = <$crate::PyTuple as $crate::PythonObject>::unchecked_downcast_from(args);
match $f(py, &args) {
Ok(val) => {
let obj = $crate::ToPyObject::into_py_object(val, py);
return $crate::ToPythonPointer::steal_ptr(obj);
}
Err(e) => {
e.restore();
return ::std::ptr::null_mut();
}
}
}
static mut [ method_def_ $f ]: $crate::_detail::ffi::PyMethodDef = $crate::_detail::ffi::PyMethodDef {
//ml_name: bytes!(stringify!($f), "\0"),
ml_name: 0 as *const $crate::_detail::libc::c_char,
ml_meth: Some([ wrap_ $f ]),
ml_flags: $crate::_detail::ffi::METH_VARARGS,
ml_doc: 0 as *const $crate::_detail::libc::c_char
};
unsafe {
$crate::_detail::py_fn_impl(&mut [ method_def_ $f ])
}
}})
}
pub struct PyFn(*mut ffi::PyMethodDef);
pub unsafe fn py_fn_impl(def: *mut ffi::PyMethodDef) -> PyFn {
PyFn(def)
}
impl <'p> ToPyObject<'p> for PyFn {
type ObjectType = PyObject<'p>;
fn to_py_object(&self, py: Python<'p>) -> PyObject<'p> {
unsafe {
err::from_owned_ptr_or_panic(py, ffi::PyCFunction_New(self.0, ptr::null_mut()))
}
}
}

View file

@ -113,6 +113,7 @@ mod conversion;
mod objects;
mod objectprotocol;
mod pythonrun;
mod function;
#[cfg(feature="python27-sys")]
mod rustobject;
@ -123,6 +124,9 @@ pub mod _detail {
pub use libc;
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;
/// assume_gil_acquired(), but the returned Python<'p> is bounded by the scope
/// of the referenced variable.
@ -282,66 +286,3 @@ pub unsafe fn py_module_initializer_impl(
})
}
/// Creates a python callable object that invokes a Rust function.
///
/// As arguments, takes the name of a rust function with the signature
/// `<'p>(Python<'p>, &PyTuple<'p>) -> PyResult<'p, T>`
/// for some `T` that implements `ToPyObject`.
///
/// Returns a type that implements `ToPyObject` by producing a python callable.
///
/// See `py_module_initializer!` for example usage.
#[macro_export]
macro_rules! py_fn {
($f: ident) => ( interpolate_idents! {{
unsafe extern "C" fn [ wrap_ $f ](
_slf: *mut $crate::_detail::ffi::PyObject,
args: *mut $crate::_detail::ffi::PyObject)
-> *mut $crate::_detail::ffi::PyObject
{
let _guard = $crate::_detail::PanicGuard::with_message("Rust panic in py_fn!");
let py = $crate::_detail::bounded_assume_gil_acquired(&args);
let args = $crate::PyObject::from_borrowed_ptr(py, args);
let args = <$crate::PyTuple as $crate::PythonObject>::unchecked_downcast_from(args);
match $f(py, &args) {
Ok(val) => {
let obj = $crate::ToPyObject::into_py_object(val, py);
return $crate::ToPythonPointer::steal_ptr(obj);
}
Err(e) => {
e.restore();
return ::std::ptr::null_mut();
}
}
}
static mut [ method_def_ $f ]: $crate::_detail::ffi::PyMethodDef = $crate::_detail::ffi::PyMethodDef {
//ml_name: bytes!(stringify!($f), "\0"),
ml_name: b"<rust function>\0" as *const u8 as *const $crate::_detail::libc::c_char,
ml_meth: Some([ wrap_ $f ]),
ml_flags: $crate::_detail::ffi::METH_VARARGS,
ml_doc: 0 as *const $crate::_detail::libc::c_char
};
unsafe { $crate::PyRustFunctionHandle::new(&mut [ method_def_ $f ]) }
}})
}
#[doc(hidden)]
pub struct PyRustFunctionHandle(*mut ffi::PyMethodDef);
impl PyRustFunctionHandle {
#[inline]
pub unsafe fn new(p: *mut ffi::PyMethodDef) -> PyRustFunctionHandle {
PyRustFunctionHandle(p)
}
}
impl <'p> ToPyObject<'p> for PyRustFunctionHandle {
type ObjectType = PyObject<'p>;
fn to_py_object(&self, py: Python<'p>) -> PyObject<'p> {
unsafe {
err::from_owned_ptr_or_panic(py, ffi::PyCFunction_New(self.0, ptr::null_mut()))
}
}
}

View file

@ -1,2 +1,119 @@
use python::Python;
// Copyright (c) 2015 Daniel Grunwald
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
use std::{ptr, marker};
use python::{Python, PythonObject};
use objects::{PyObject, PyTuple, PyType};
use conversion::ToPyObject;
use super::typebuilder::TypeMember;
use ffi;
use err;
/// Creates a python instance method descriptor that invokes a Rust function.
///
/// As arguments, takes the name of a rust function with the signature
/// `for<'p> fn(&PyRustObject<'p, _>, &PyTuple<'p>) -> PyResult<'p, T>`
/// for some `T` that implements `ToPyObject`.
///
/// Returns a type that implements `pythonobject::TypeMember<PyRustObject<_>>`
/// by producing an instance method descriptor.
///
/// # Example
/// ```
/// #![feature(plugin)]
/// #![plugin(interpolate_idents)]
/// #[macro_use] extern crate cpython;
/// use cpython::{Python, PythonObject, PyResult, PyErr, ObjectProtocol,
/// PyTuple, PyRustObject, PyRustTypeBuilder};
/// use cpython::{exc};
///
/// fn mul<'p>(slf: &PyRustObject<'p, i32>, args: &PyTuple<'p>) -> PyResult<'p, i32> {
/// let py = slf.python();
/// match slf.get().checked_mul(try!(args.get_item(0).extract::<i32>())) {
/// Some(val) => Ok(val),
/// None => Err(PyErr::new_lazy_init(py.get_type::<exc::OverflowError>(), None))
/// }
/// }
///
/// fn main() {
/// let gil = Python::acquire_gil();
/// let multiplier_type = PyRustTypeBuilder::<i32>::new(gil.python(), "Multiplier")
/// .add("mul", py_method!(mul))
/// .finish().unwrap();
/// let obj = multiplier_type.create_instance(3, ()).into_object();
/// let result = obj.call_method("mul", &(4,), None).unwrap().extract::<i32>().unwrap();
/// assert_eq!(result, 12);
/// }
/// ```
#[macro_export]
macro_rules! py_method {
($f: ident) => ( interpolate_idents! {{
unsafe extern "C" fn [ wrap_ $f ](
slf: *mut $crate::_detail::ffi::PyObject,
args: *mut $crate::_detail::ffi::PyObject)
-> *mut $crate::_detail::ffi::PyObject
{
let _guard = $crate::_detail::PanicGuard::with_message("Rust panic in py_method!");
let py = $crate::_detail::bounded_assume_gil_acquired(&args);
let slf = $crate::PyObject::from_borrowed_ptr(py, slf);
let slf = $crate::PythonObject::unchecked_downcast_from(slf);
let args = $crate::PyObject::from_borrowed_ptr(py, args);
let args = <$crate::PyTuple as $crate::PythonObject>::unchecked_downcast_from(args);
match $f(&slf, &args) {
Ok(val) => {
let obj = $crate::ToPyObject::into_py_object(val, py);
return $crate::ToPythonPointer::steal_ptr(obj);
}
Err(e) => {
e.restore();
return ::std::ptr::null_mut();
}
}
}
static mut [ method_def_ $f ]: $crate::_detail::ffi::PyMethodDef = $crate::_detail::ffi::PyMethodDef {
//ml_name: bytes!(stringify!($f), "\0"),
ml_name: b"<rust method>\0" as *const u8 as *const $crate::_detail::libc::c_char,
ml_meth: Some([ wrap_ $f ]),
ml_flags: $crate::_detail::ffi::METH_VARARGS,
ml_doc: 0 as *const $crate::_detail::libc::c_char
};
unsafe { $crate::_detail::py_method_impl(&mut [ method_def_ $f ], $f) }
}})
}
pub struct MethodDescriptor<T>(*mut ffi::PyMethodDef, marker::PhantomData<fn(&T)>);
// py_method_impl takes fn(&T) to ensure that the T in MethodDescriptor<T>
// corresponds to the T in the function signature.
pub unsafe fn py_method_impl<'p, T, R>(
def: *mut ffi::PyMethodDef,
_f: fn(&T, &PyTuple<'p>) -> err::PyResult<'p, R>
) -> MethodDescriptor<T> {
MethodDescriptor(def, marker::PhantomData)
}
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> {
unsafe {
err::from_owned_ptr_or_panic(ty.python(),
ffi::PyDescr_NewMethod(ty.as_type_ptr(), self.0))
}
}
}

View file

@ -25,7 +25,9 @@ use std::{mem, ops, ptr, marker};
use err::{self, PyResult};
pub mod typebuilder;
mod method;
pub mod method;
#[cfg(test)]
mod tests;
/// A PythonObject that is usable as a base type with PyTypeBuilder::base().
pub trait PythonBaseObject<'p> : PythonObject<'p> {

View file

@ -1,8 +1,24 @@
extern crate cpython;
// Copyright (c) 2015 Daniel Grunwald
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use cpython::*;
use {Python, NoArgs, PythonObject, PyRustTypeBuilder};
#[test]
fn rustobject_calls_drop() {
@ -29,7 +45,7 @@ fn rustobject_calls_drop() {
#[test]
fn rustobject_no_init_from_python() {
fn no_init_from_python() {
let gil = Python::acquire_gil();
let py = gil.python();
let t = PyRustTypeBuilder::<i32>::new(py, "MyType").finish().unwrap();
@ -38,7 +54,7 @@ fn rustobject_no_init_from_python() {
#[test]
fn rustobject_heaptype_refcount() {
fn heaptype_refcount() {
let gil = Python::acquire_gil();
let py = gil.python();
let t = PyRustTypeBuilder::<i32>::new(py, "MyType").finish().unwrap();

View file

@ -1,3 +1,21 @@
// Copyright (c) 2015 Daniel Grunwald
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
use libc;
use ffi;
use python::{Python, ToPythonPointer, PythonObject};