Add support for exposing rust functions to python

This commit is contained in:
Daniel Grunwald 2015-04-18 20:17:25 +02:00
parent 60360fa03f
commit f8e24a870c
13 changed files with 147 additions and 23 deletions

1
.gitignore vendored
View File

@ -1,4 +1,3 @@
/target
/Cargo.lock
/testmodule.so

12
examples/hello.rs Normal file
View File

@ -0,0 +1,12 @@
#[macro_use] extern crate cpython;
use cpython::{PythonObject, ObjectProtocol, PyModule, Python};
fn main() {
let gil = Python::acquire_gil();
let py = gil.python();
let sys = PyModule::import(py, cstr!("sys")).unwrap();
let path: String = sys.as_object().getattr("version").unwrap().extract().unwrap();
println!("Hello Python {}", path);
}

4
extensions/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
stamps
*.out
*.so

32
extensions/Makefile Normal file
View File

@ -0,0 +1,32 @@
TARGETDIR=../target/debug
rwildcard=$(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2) $(filter $(subst *,%,$2),$d))
.PHONY: all clean
all:
clean:
-rm *.so
-rm *.out
-rm -r stamps
stamps:
mkdir stamps
stamps/rust-cpython: $(call rwildcard,../src,*.rs) Makefile | stamps
cd .. && cargo build
touch $@
%.so: %.rs stamps/rust-cpython
rustc $< -L $(TARGETDIR) -L $(TARGETDIR)/deps -o $@
hello.out: hello.so
python -c "import hello; hello.run(hello.val())" 2>&1 | tee $@
all: stamps/test-hello
stamps/test-hello: hello.out
@grep "Rust says: Hello Python!" hello.out >/dev/null
@grep "Rust got 42" hello.out >/dev/null

30
extensions/hello.rs Normal file
View File

@ -0,0 +1,30 @@
#![crate_type = "dylib"]
#[macro_use] extern crate cpython;
use cpython::{PyObject, PyResult, PyModule, Python, PyTuple};
py_module_initializer!("hello", inithello, |py, m| {
try!(m.add(cstr!("__doc__"), "Module documentation string"));
try!(m.add(cstr!("run"), py_func!(py, run)));
try!(add_val(py, &m));
Ok(())
});
fn run<'p>(py: Python<'p>, args: &PyTuple<'p>) -> PyResult<'p, PyObject<'p>> {
println!("Rust says: Hello Python!");
for arg in args {
println!("Rust got {}", arg);
}
Ok(py.None())
}
fn val<'p>(py: Python<'p>, args: &PyTuple<'p>) -> PyResult<'p, i32> {
Ok(42)
}
// Workaround for Rust #24561
fn add_val<'p>(py: Python<'p>, m: &PyModule<'p>) -> PyResult<'p, ()> {
m.add(cstr!("val"), py_func!(py, val))
}

View File

@ -207,6 +207,16 @@ pub unsafe fn result_from_owned_ptr(py : Python, p : *mut ffi::PyObject) -> PyRe
}
}
#[inline]
pub unsafe fn from_owned_ptr_or_panic(py : Python, p : *mut ffi::PyObject) -> PyObject {
if p.is_null() {
ffi::PyErr_Print();
panic!("Python API called failed");
} else {
PyObject::from_owned_ptr(py, p)
}
}
/// Construct PyObject from the result of a python FFI call that returns a borrowed reference.
/// Returns Err(PyErr) if the pointer is null.
/// Unsafe because the pointer might be invalid.

View File

@ -12,9 +12,9 @@ pub use ffi::Py_ssize_t;
pub use err::{PyErr, PyResult};
pub use objects::*;
pub use python::{Python, PythonObject, PythonObjectWithCheckedDowncast, PythonObjectWithTypeObject};
pub use pythonrun::{GILGuard, prepare_freethreaded_python};
pub use conversion::{FromPyObject, ToPyObject};
pub use objectprotocol::{ObjectProtocol};
pub use std::ffi::CStr;
#[macro_export]
macro_rules! cstr(
@ -33,6 +33,14 @@ mod objects;
mod objectprotocol;
mod pythonrun;
/// Private re-exports for use in macros.
pub mod _detail {
pub use ffi;
pub use libc;
pub use python::ToPythonPointer;
pub use err::from_owned_ptr_or_panic;
}
#[macro_export]
macro_rules! py_module_initializer {
($name: tt, $init_funcname: ident, $init: expr) => {
@ -47,6 +55,41 @@ macro_rules! py_module_initializer {
}
}
#[macro_export]
macro_rules! py_func {
($py: expr, $f: expr) => ({
unsafe extern "C" fn wrap_py_func
(_slf: *mut $crate::_detail::ffi::PyObject, args: *mut $crate::_detail::ffi::PyObject)
-> *mut $crate::_detail::ffi::PyObject {
let py = $crate::Python::assume_gil_acquired();
let args = $crate::PyObject::from_borrowed_ptr(py, args);
let args: &PyTuple = $crate::PythonObject::unchecked_downcast_borrow_from(&args);
let result = match $f(py, args) {
Ok(val) => $crate::ToPyObject::into_py_object(val, py),
Err(e) => Err(e)
};
match result {
Ok(val) => $crate::_detail::ToPythonPointer::steal_ptr(val),
Err(e) => { e.restore(); ::std::ptr::null_mut() }
}
}
static mut method_def: $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_py_func),
ml_flags: $crate::_detail::ffi::METH_VARARGS,
ml_doc: 0 as *const $crate::_detail::libc::c_char
};
let py: Python = $py;
unsafe {
let obj = $crate::_detail::ffi::PyCFunction_New(&mut method_def, ::std::ptr::null_mut());
$crate::_detail::from_owned_ptr_or_panic(py, obj)
}
})
}
#[test]
fn it_works() {
let gil = Python::acquire_gil();

View File

@ -39,7 +39,7 @@ macro_rules! exc_type(
impl <'p> PythonObjectWithTypeObject<'p> for $name<'p> {
#[inline]
fn type_object(py: Python<'p>, _ : Option<&Self>) -> PyType<'p> {
fn type_object(py: Python<'p>) -> PyType<'p> {
unsafe { PyType::from_type_ptr(py, ffi::$exc_name as *mut ffi::PyTypeObject) }
}
}

View File

@ -85,7 +85,7 @@ macro_rules! pyobject_newtype(
impl <'p> ::python::PythonObjectWithTypeObject<'p> for $name<'p> {
#[inline]
fn type_object(py: ::python::Python<'p>, _ : Option<&Self>) -> ::objects::typeobject::PyType<'p> {
fn type_object(py: ::python::Python<'p>) -> ::objects::typeobject::PyType<'p> {
unsafe { ::objects::typeobject::PyType::from_type_ptr(py, &mut ::ffi::$typeobject) }
}
}

View File

@ -73,7 +73,7 @@ impl <'p> PythonObjectWithCheckedDowncast<'p> for PyObject<'p> {
impl <'p> PythonObjectWithTypeObject<'p> for PyObject<'p> {
#[inline]
fn type_object(py: Python<'p>, _ : Option<&Self>) -> PyType<'p> {
fn type_object(py: Python<'p>) -> PyType<'p> {
unsafe { PyType::from_type_ptr(py, &mut ffi::PyBaseObject_Type) }
}
}

View File

@ -65,6 +65,16 @@ impl<'p> std::ops::Index<usize> for PyTuple<'p> {
}
}
impl<'p, 'a> std::iter::IntoIterator for &'a PyTuple<'p> {
type Item = &'a PyObject<'p>;
type IntoIter = std::slice::Iter<'a, PyObject<'p>>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
fn wrong_tuple_length<'p>(t: &PyTuple<'p>, expected_length: usize) -> PyErr<'p> {
let py = t.python();

View File

@ -68,8 +68,7 @@ pub trait PythonObjectWithCheckedDowncast<'p> : PythonObject<'p> {
/// Trait implemented by python object types that have a corresponding type object.
pub trait PythonObjectWithTypeObject<'p> : PythonObjectWithCheckedDowncast<'p> {
/// Retrieves the type object for this python object type.
#[unstable = "Option<&Self> will disappear when UFCS is implemented"]
fn type_object(Python<'p>, Option<&Self>) -> PyType<'p>;
fn type_object(Python<'p>) -> PyType<'p>;
}
/// ToPythonPointer for borrowed python pointers.
@ -157,8 +156,7 @@ impl<'p> Python<'p> {
/// Retrieves a reference to the type object for type T.
#[inline]
pub fn get_type<T>(self) -> PyType<'p> where T: PythonObjectWithTypeObject<'p> {
let none : Option<&T> = None;
PythonObjectWithTypeObject::type_object(self, none)
T::type_object(self)
}
/// Import the python module with the specified name.

View File

@ -1,14 +0,0 @@
#![crate_type = "dylib"]
#[macro_use] extern crate cpython;
use cpython::{PyModule, PyResult, Python};
py_module_initializer!("testmodule", inittestmodule, |py, m| {
println!("in initializer");
try!(m.add(cstr!("__doc__"), "Module documentation string"));
try!(m.add(cstr!("__author__"), "Daniel Grunwald"));
try!(m.add(cstr!("__version__"), "0.0.1"));
Ok(())
});