From f8e24a870cf345afdbee8226cad38c921132c0d5 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sat, 18 Apr 2015 20:17:25 +0200 Subject: [PATCH] Add support for exposing rust functions to python --- .gitignore | 1 - examples/hello.rs | 12 ++++++++++++ extensions/.gitignore | 4 ++++ extensions/Makefile | 32 ++++++++++++++++++++++++++++++ extensions/hello.rs | 30 +++++++++++++++++++++++++++++ src/err.rs | 10 ++++++++++ src/lib.rs | 45 ++++++++++++++++++++++++++++++++++++++++++- src/objects/exc.rs | 2 +- src/objects/mod.rs | 2 +- src/objects/object.rs | 2 +- src/objects/tuple.rs | 10 ++++++++++ src/python.rs | 6 ++---- testmodule.rs | 14 -------------- 13 files changed, 147 insertions(+), 23 deletions(-) create mode 100644 examples/hello.rs create mode 100644 extensions/.gitignore create mode 100644 extensions/Makefile create mode 100644 extensions/hello.rs delete mode 100644 testmodule.rs diff --git a/.gitignore b/.gitignore index 81c01aa2..79034fbf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ /target /Cargo.lock -/testmodule.so diff --git a/examples/hello.rs b/examples/hello.rs new file mode 100644 index 00000000..6e7a3cd7 --- /dev/null +++ b/examples/hello.rs @@ -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); +} + diff --git a/extensions/.gitignore b/extensions/.gitignore new file mode 100644 index 00000000..9fcf9fda --- /dev/null +++ b/extensions/.gitignore @@ -0,0 +1,4 @@ +stamps +*.out +*.so + diff --git a/extensions/Makefile b/extensions/Makefile new file mode 100644 index 00000000..9df3507e --- /dev/null +++ b/extensions/Makefile @@ -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 + + + diff --git a/extensions/hello.rs b/extensions/hello.rs new file mode 100644 index 00000000..fd1b11ff --- /dev/null +++ b/extensions/hello.rs @@ -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)) +} + diff --git a/src/err.rs b/src/err.rs index 072fac7e..6fa3a9a0 100644 --- a/src/err.rs +++ b/src/err.rs @@ -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. diff --git a/src/lib.rs b/src/lib.rs index 252088a1..a26e0639 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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"\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(); diff --git a/src/objects/exc.rs b/src/objects/exc.rs index 30539bea..77baa4da 100644 --- a/src/objects/exc.rs +++ b/src/objects/exc.rs @@ -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) } } } diff --git a/src/objects/mod.rs b/src/objects/mod.rs index 090b1954..de2c1001 100644 --- a/src/objects/mod.rs +++ b/src/objects/mod.rs @@ -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) } } } diff --git a/src/objects/object.rs b/src/objects/object.rs index 504a2233..b5cb59e0 100644 --- a/src/objects/object.rs +++ b/src/objects/object.rs @@ -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) } } } diff --git a/src/objects/tuple.rs b/src/objects/tuple.rs index 88661347..8a4ecbc9 100644 --- a/src/objects/tuple.rs +++ b/src/objects/tuple.rs @@ -65,6 +65,16 @@ impl<'p> std::ops::Index 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(); diff --git a/src/python.rs b/src/python.rs index 461c13d1..9809f2f6 100644 --- a/src/python.rs +++ b/src/python.rs @@ -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(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. diff --git a/testmodule.rs b/testmodule.rs deleted file mode 100644 index 729fa27c..00000000 --- a/testmodule.rs +++ /dev/null @@ -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(()) -}); -