diff --git a/examples/hello.rs b/examples/hello.rs index 6e7a3cd7..da3d6638 100644 --- a/examples/hello.rs +++ b/examples/hello.rs @@ -5,7 +5,7 @@ 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 sys = PyModule::import(py, "sys").unwrap(); let path: String = sys.as_object().getattr("version").unwrap().extract().unwrap(); println!("Hello Python {}", path); } diff --git a/extensions/hello.rs b/extensions/hello.rs index fd1b11ff..f415eec8 100644 --- a/extensions/hello.rs +++ b/extensions/hello.rs @@ -5,8 +5,8 @@ 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!(m.add("__doc__", "Module documentation string")); + try!(m.add("run", py_func!(py, run))); try!(add_val(py, &m)); Ok(()) }); @@ -19,12 +19,12 @@ fn run<'p>(py: Python<'p>, args: &PyTuple<'p>) -> PyResult<'p, PyObject<'p>> { Ok(py.None()) } -fn val<'p>(py: Python<'p>, args: &PyTuple<'p>) -> PyResult<'p, i32> { +fn val<'p>(_: Python<'p>, _: &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)) + m.add("val", py_func!(py, val)) } diff --git a/src/conversion.rs b/src/conversion.rs index d036de37..ebf661bf 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -70,16 +70,16 @@ pub trait FromPyObject<'p, 's> { // This allows using existing python objects in code that generically expects a value // convertible to a python object. -impl <'p, T> ToPyObject<'p> for T where T: PythonObject<'p> { - type ObjectType = T; +impl <'p> ToPyObject<'p> for PyObject<'p> { + type ObjectType = PyObject<'p>; #[inline] - fn to_py_object(&self, py: Python<'p>) -> PyResult<'p, T> { + fn to_py_object(&self, py: Python<'p>) -> PyResult<'p, PyObject<'p>> { Ok(self.clone()) } #[inline] - fn into_py_object(self, py: Python<'p>) -> PyResult<'p, T> { + fn into_py_object(self, py: Python<'p>) -> PyResult<'p, PyObject<'p>> { Ok(self) } @@ -101,7 +101,6 @@ impl <'p, 's, T> FromPyObject<'p, 's> for T where T: PythonObjectWithCheckedDown // We support FromPyObject and ToPyObject for borrowed python references. // This allows using existing python objects in code that generically expects a value // convertible to a python object. -/* impl <'p, 's, T> ToPyObject<'p> for &'s T where T : ToPyObject<'p> { type ObjectType = >::ObjectType; @@ -121,5 +120,5 @@ impl <'p, 's, T> ToPyObject<'p> for &'s T where T : ToPyObject<'p> { (**self).with_borrowed_ptr(py, f) } } -*/ + diff --git a/src/lib.rs b/src/lib.rs index a26e0639..1002e3d2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,17 +6,34 @@ #![feature(utf8_error)] // for translating Utf8Error to python exception #![allow(unused_imports, dead_code, unused_variables)] +//! Rust bindings to the python interpreter. +//! +//! # Example +//! ``` +//! #[macro_use] extern crate cpython; +//! +//! use cpython::{PythonObject, ObjectProtocol, PyModule, Python}; +//! +//! fn main() { +//! let gil = Python::acquire_gil(); +//! let py = gil.python(); +//! let sys = py.import("sys").unwrap(); +//! let version: String = sys.get("version").unwrap().extract().unwrap(); +//! println!("Hello Python {}", version); +//! } +//! ``` + extern crate libc; extern crate python27_sys as ffi; pub use ffi::Py_ssize_t; pub use err::{PyErr, PyResult}; pub use objects::*; -pub use python::{Python, PythonObject, PythonObjectWithCheckedDowncast, PythonObjectWithTypeObject}; +pub use python::{Python, PythonObject, PythonObjectWithCheckedDowncast, PythonObjectWithTypeObject, ToPythonPointer}; pub use pythonrun::{GILGuard, prepare_freethreaded_python}; pub use conversion::{FromPyObject, ToPyObject}; pub use objectprotocol::{ObjectProtocol}; -#[macro_export] +/// Constructs a `&'static CStr` literal. macro_rules! cstr( ($s: tt) => ( // TODO: verify that $s is a string literal without nuls @@ -33,21 +50,66 @@ mod objects; mod objectprotocol; mod pythonrun; -/// Private re-exports for use in macros. +/// Private re-exports for macros. Do not use. pub mod _detail { pub use ffi; pub use libc; - pub use python::ToPythonPointer; pub use err::from_owned_ptr_or_panic; } +/// Expands to an `extern "C"` function that allows python to load +/// the rust code as a python extension module. +/// +/// The macro takes three arguments: +/// +/// 1. The module name as a string literal. +/// 2. The name of the init function as an identifier. +/// The function must be named `init$module_name` so that python 2.7 can load the module. +/// Note: this parameter will be removed in a future version +/// (once Rust supports `concat_ident!` as function name). +/// 3. A function or lambda of type `Fn(Python<'p>, &PyModule<'p>) -> PyResult<'p, ()>`. +/// This function will be called when the module is imported, and is responsible +/// for adding the module's members. +/// +/// # Example +/// ``` +/// #![crate_type = "dylib"] +/// #[macro_use] extern crate cpython; +/// use cpython::{Python, PyResult, PyObject, PyTuple}; +/// +/// py_module_initializer!("example", initexample, |py, m| { +/// try!(m.add("__doc__", "Module documentation string")); +/// try!(m.add("run", py_func!(py, run))); +/// Ok(()) +/// }); +/// +/// fn run<'p>(py: Python<'p>, args: &PyTuple<'p>) -> PyResult<'p, PyObject<'p>> { +/// println!("Rust says: Hello Python!"); +/// Ok(py.None()) +/// } +/// # fn main() {} +/// ``` +/// The code must be compiled into a file `example.so`. +/// +/// ```bash +/// rustc example.rs -o example.so +/// ``` +/// It can then be imported into python: +/// +/// ```python +/// >>> import example +/// >>> example.run() +/// Rust says: Hello Python! +/// ``` +/// #[macro_export] macro_rules! py_module_initializer { ($name: tt, $init_funcname: ident, $init: expr) => { #[no_mangle] pub extern "C" fn $init_funcname() { let py = unsafe { $crate::Python::assume_gil_acquired() }; - match $crate::PyModule::_init(py, cstr!($name), $init) { + let name = unsafe { ::std::ffi::CStr::from_ptr(concat!($name, "\0").as_ptr() as *const _) }; + match $crate::PyModule::_init(py, name, $init) { Ok(()) => (), Err(e) => e.restore() } @@ -63,13 +125,13 @@ macro_rules! py_func { -> *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 args: &$crate::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), + Ok(val) => $crate::ToPythonPointer::steal_ptr(val), Err(e) => { e.restore(); ::std::ptr::null_mut() } } } @@ -88,14 +150,3 @@ macro_rules! py_func { }) } - - -#[test] -fn it_works() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let sys = PyModule::import(py, cstr!("sys")).unwrap(); - let path = sys.as_object().getattr("path").unwrap(); - println!("{0}", path); -} - diff --git a/src/objects/module.rs b/src/objects/module.rs index d8a4f6a3..bb6dbbee 100644 --- a/src/objects/module.rs +++ b/src/objects/module.rs @@ -5,22 +5,22 @@ use python::{Python, PythonObject, ToPythonPointer}; use conversion::ToPyObject; use objects::{PyObject, PyType, PyDict, exc}; use err::{self, PyResult, PyErr}; -use std::ffi::CStr; +use std::ffi::{CStr, CString}; pyobject_newtype!(PyModule, PyModule_Check, PyModule_Type); impl <'p> PyModule<'p> { /// Create a new module object with the __name__ attribute set to name. - /// Only the module’s __doc__ and __name__ attributes are filled in; - /// the caller is responsible for providing a __file__ attribute. - pub fn new(py: Python<'p>, name: &CStr) -> PyResult<'p, PyModule<'p>> { + pub fn new(py: Python<'p>, name: &str) -> PyResult<'p, PyModule<'p>> { + let name = CString::new(name).unwrap(); unsafe { err::result_cast_from_owned_ptr(py, ffi::PyModule_New(name.as_ptr())) } } /// Import the python module with the specified name. - pub fn import(py: Python<'p>, name: &CStr) -> PyResult<'p, PyModule<'p>> { + pub fn import(py: Python<'p>, name: &str) -> PyResult<'p, PyModule<'p>> { + let name = CString::new(name).unwrap(); unsafe { err::result_cast_from_owned_ptr(py, ffi::PyImport_ImportModule(name.as_ptr())) } @@ -68,10 +68,17 @@ impl <'p> PyModule<'p> { unsafe { self.str_from_ptr(ffi::PyModule_GetFilename(self.as_ptr())) } } + /// Convenience function for retrieving a member from the module. + pub fn get(&self, name: &str) -> PyResult<'p, PyObject<'p>> { + use objectprotocol::ObjectProtocol; + self.as_object().getattr(name) + } + /// Adds a member to the module. /// This is a convenience function which can be used from the module's initialization function. - pub fn add(&self, name: &CStr, value: V) -> PyResult<'p, ()> where V: ToPyObject<'p> { + pub fn add(&self, name: &str, value: V) -> PyResult<'p, ()> where V: ToPyObject<'p> { let py = self.python(); + let name = CString::new(name).unwrap(); let value = try!(value.into_py_object(py)); let r = unsafe { ffi::PyModule_AddObject(self.as_ptr(), name.as_ptr(), value.steal_ptr()) }; err::error_on_minusone(py, r) diff --git a/src/python.rs b/src/python.rs index 9809f2f6..258f8601 100644 --- a/src/python.rs +++ b/src/python.rs @@ -132,35 +132,34 @@ impl<'p> Python<'p> { result } - /// Retrieves a reference to the special 'None' value. + /// Gets the python builtin value `None`. #[allow(non_snake_case)] // the python keyword starts with uppercase #[inline] pub fn None(self) -> PyObject<'p> { unsafe { PyObject::from_borrowed_ptr(self, ffi::Py_None()) } } - /// Retrieves a reference to the 'True' constant value. + /// Gets the python builtin value `True`. #[allow(non_snake_case)] // the python keyword starts with uppercase #[inline] pub fn True(self) -> PyBool<'p> { unsafe { PyObject::from_borrowed_ptr(self, ffi::Py_True()).unchecked_cast_into::() } } - /// Retrieves a reference to the 'False' constant value. + /// Gets the python builtin value `False`. #[allow(non_snake_case)] // the python keyword starts with uppercase #[inline] pub fn False(self) -> PyBool<'p> { unsafe { PyObject::from_borrowed_ptr(self, ffi::Py_False()).unchecked_cast_into::() } } - - /// Retrieves a reference to the type object for type T. - #[inline] + + /// Gets the python type object for type T. pub fn get_type(self) -> PyType<'p> where T: PythonObjectWithTypeObject<'p> { T::type_object(self) } /// Import the python module with the specified name. - pub fn import(self, name : &CStr) -> PyResult<'p, PyModule<'p>> { + pub fn import(self, name : &str) -> PyResult<'p, PyModule<'p>> { PyModule::import(self, name) } } diff --git a/src/pythonrun.rs b/src/pythonrun.rs index d0c798e2..e8b2144e 100644 --- a/src/pythonrun.rs +++ b/src/pythonrun.rs @@ -6,6 +6,12 @@ use python::Python; static START: Once = ONCE_INIT; /// Prepares the use of python in a free-threaded context. +/// +/// If the python interpreter is not already initialized, this function +/// will initialize it with disabled signal handling +/// (python will not raise the `KeyboardInterrupt` exception). +/// Python signal handling depends on the notion of a 'main thread', which must be +/// the thread that initializes the python interpreter. pub fn prepare_freethreaded_python() { // Protect against race conditions when python is not yet initialized // and multiple threads concurrently call 'prepare_freethreaded_python()'. @@ -43,8 +49,11 @@ pub struct GILGuard { gstate: ffi::PyGILState_STATE } +/// GILGuard is not Send because the GIL must be released +/// by the same thread that acquired it. impl !Send for GILGuard {} +/// The Drop implementation for GILGuard will release the GIL. impl Drop for GILGuard { fn drop(&mut self) { unsafe { ffi::PyGILState_Release(self.gstate) } @@ -61,7 +70,8 @@ impl GILGuard { let gstate = unsafe { ffi::PyGILState_Ensure() }; // acquire GIL GILGuard { gstate: gstate } } - + + /// Retrieves the marker type that proves that the GIL was acquired. pub fn python<'p>(&'p self) -> Python<'p> { unsafe { Python::assume_gil_acquired() } }