From a23b5b591060574de91067d77081f56082aa21b2 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Mon, 3 Aug 2015 00:06:15 +0200 Subject: [PATCH] Add parameter extraction support to `py_fn!` and `py_method!`. These macros now support specifying an argument list: `py_fn!(myfn(myarg: i32))` will expect `myfn` to be a function with the signature: `fn myfn<'p>(py: Python<'p>, myarg: i32) -> PyResult<'p, _>` It can called from python as `myfn(1)` or using keyword arguments: `myfn(myarg=1)`. If no parameter list is specified (`py_fn!(myfn)`), the expected signature now includes the keyword arguments: `fn run<'p>(py: Python<'p>, args: &PyTuple<'p>, kwargs: Option<&PyDict<'p>>)` Due to the additional `kwargs` argument, this is a [breaking-change]. --- extensions/custom_type.rs | 8 +- extensions/hello.rs | 13 ++- src/argparse.rs | 73 ++++++++---- src/conversion.rs | 2 +- src/function.rs | 74 ++++++++++-- src/lib.rs | 7 +- src/rustobject/method.rs | 235 ++++++++++++++++++++++++++++++++------ 7 files changed, 335 insertions(+), 77 deletions(-) diff --git a/extensions/custom_type.rs b/extensions/custom_type.rs index e6443dc4..5026931c 100644 --- a/extensions/custom_type.rs +++ b/extensions/custom_type.rs @@ -4,18 +4,18 @@ #[macro_use] extern crate cpython; -use cpython::{PythonObject, PyObject, PyRustObject, PyTuple, PyResult}; +use cpython::{PythonObject, PyObject, PyRustObject, PyResult}; py_module_initializer!(custom_type, |_py, m| { try!(m.add("__doc__", "Module documentation string")); try!(m.add_type::("MyType") - .add("a", py_method!(a)) + .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()); +fn a<'p>(slf: &PyRustObject<'p, i32>) -> PyResult<'p, PyObject<'p>> { + println!("a() was called with self={:?}", slf.get()); Ok(slf.python().None()) } diff --git a/extensions/hello.rs b/extensions/hello.rs index 78a66f40..407052d1 100644 --- a/extensions/hello.rs +++ b/extensions/hello.rs @@ -4,24 +4,29 @@ #[macro_use] extern crate cpython; -use cpython::{PyObject, PyResult,Python, PyTuple}; +use cpython::{PyObject, PyResult, Python, PyTuple, PyDict}; py_module_initializer!(hello, |_py, m| { try!(m.add("__doc__", "Module documentation string")); try!(m.add("run", py_fn!(run))); - try!(m.add("val", py_fn!(val))); + try!(m.add("val", py_fn!(val()))); Ok(()) }); -fn run<'p>(py: Python<'p>, args: &PyTuple<'p>) -> PyResult<'p, PyObject<'p>> { +fn run<'p>(py: Python<'p>, args: &PyTuple<'p>, kwargs: Option<&PyDict<'p>>) -> PyResult<'p, PyObject<'p>> { println!("Rust says: Hello Python!"); for arg in args { println!("Rust got {}", arg); } + if let Some(kwargs) = kwargs { + for (key, val) in kwargs.items() { + println!("{} = {}", key, val); + } + } Ok(py.None()) } -fn val<'p>(_: Python<'p>, _: &PyTuple<'p>) -> PyResult<'p, i32> { +fn val<'p>(_: Python<'p>) -> PyResult<'p, i32> { Ok(42) } diff --git a/src/argparse.rs b/src/argparse.rs index 65f3b18a..86e85067 100644 --- a/src/argparse.rs +++ b/src/argparse.rs @@ -17,6 +17,7 @@ // DEALINGS IN THE SOFTWARE. //! This module contains logic for parsing a python argument list. +//! See also the macros `py_argparse!`, `py_fn` and `py_method`. use std::ptr; use python::{Python, PythonObject}; @@ -25,19 +26,22 @@ use conversion::ToPyObject; use ffi; use err::{self, PyResult}; +/// Description of a python parameter; used for `parse_args()`. pub struct ParamDescription<'a> { - name: &'a str, - is_optional: bool + /// The name of the parameter. + pub name: &'a str, + /// Whether the parameter is optional. + pub is_optional: bool } /// Parse argument list /// -/// fname: Name of the current function -/// params: Declared parameters of the function -/// args: Positional arguments -/// kwargs: Keyword arguments -/// output: Output array that receives the arguments. -/// Must have same length as `params` and must be initialized to `None`. +/// * fname: Name of the current function +/// * params: Declared parameters of the function +/// * args: Positional arguments +/// * kwargs: Keyword arguments +/// * output: Output array that receives the arguments. +/// Must have same length as `params` and must be initialized to `None`. pub fn parse_args<'p>( fname: Option<&str>, params: &[ParamDescription], args: &PyTuple<'p>, kwargs: Option<&PyDict<'p>>, @@ -99,10 +103,12 @@ pub fn parse_args<'p>( Ok(()) } -macro_rules! argparse_extract { - ( ( ) $body:block ) => { $body }; - ( ( $pname:ident : $ptype:ty ) $body:block) => { - match <$ptype as $crate::ExtractPyObject>::prepare_extract($pname.as_ref().unwrap()) { +#[doc(hidden)] +#[macro_export] +macro_rules! py_argparse_extract { + ( $iter:expr, ( ) $body:block ) => { $body }; + ( $iter:expr, ( $pname:ident : $ptype:ty ) $body:block) => { + match <$ptype as $crate::ExtractPyObject>::prepare_extract($iter.next().unwrap().as_ref().unwrap()) { Ok(prepared) => { match <$ptype as $crate::ExtractPyObject>::extract(&prepared) { Ok($pname) => $body, @@ -112,18 +118,37 @@ macro_rules! argparse_extract { Err(e) => Err(e) } }; - ( ( $pname:ident : $ptype:ty , $($r:tt)+ ) $body:block) => { - argparse_extract!(($pname: $ptype) { - argparse_extract!( ( $($r)* ) $body) + ( $iter:expr, ( $pname:ident : $ptype:ty , $($r:tt)+ ) $body:block) => { + py_argparse_extract!($iter, ($pname: $ptype) { + py_argparse_extract!( $iter, ( $($r)* ) $body) }) } } -macro_rules! argparse_snd { - ( $fst:expr, $snd:expr) => { $snd } +#[doc(hidden)] +#[macro_export] +macro_rules! py_argparse_snd { + ( $fst:expr, $snd:expr ) => { $snd } } -macro_rules! argparse { +/// This macro is used to parse a parameter list into a set of variables. +/// +/// Syntax: `py_argparse!(fname, args, kwargs, (parameter-list) { body })` +/// +/// * `fname`: expression of type `Option<&str>`: Name of the function used in error messages. +/// * `args`: expression of type `&PyTuple`: The position arguments +/// * `kwargs`: expression of type `Option<&PyDict>`: The named arguments +/// * `parameter-list`: a comma-separated list of Rust parameter declarations (`name: type`). +/// The types used must implement the `ExtractPyObject` trait. +/// * `body`: expression of type `PyResult<_>`. +/// +/// `py_argparse!()` expands to code that extracts values from `args` and `kwargs` and assigns +/// them to the parameters. If the extraction is successful, `py_argparse!()` evaluates +/// the body expression (where the extracted parameters are available) and returns the result +/// value of the body expression. +/// If extraction fails, `py_argparse!()` returns a failed `PyResult` without evaluating `body`. +#[macro_export] +macro_rules! py_argparse { ($fname:expr, $args:expr, $kwargs:expr, ($( $pname:ident : $ptype:ty ),*) $body:block) => {{ const PARAMS: &'static [$crate::argparse::ParamDescription<'static>] = &[ $( @@ -133,11 +158,15 @@ macro_rules! argparse { } ),* ]; - let mut output = [$( argparse_snd!($pname, None) ),*]; + let mut output = [$( py_argparse_snd!($pname, None) ),*]; match $crate::argparse::parse_args($fname, PARAMS, $args, $kwargs, &mut output) { Ok(()) => { - let &[$(ref $pname),*] = &output; - argparse_extract!( ( $( $pname : $ptype ),* ) $body ) + // We can't use experimental slice pattern syntax in macros + //let &[$(ref $pname),*] = &output; + let mut iter = output.iter(); + let ret = py_argparse_extract!( iter, ( $( $pname : $ptype ),* ) $body ); + assert!(iter.next() == None); + ret }, Err(e) => Err(e) } @@ -155,7 +184,7 @@ mod test { let py = gil_guard.python(); let mut called = false; let tuple = ("abc", 42).to_py_object(py); - argparse!(None, &tuple, None, (x: &str, y: i32) { + py_argparse!(None, &tuple, None, (x: &str, y: i32) { assert_eq!(x, "abc"); assert_eq!(y, 42); called = true; diff --git a/src/conversion.rs b/src/conversion.rs index 771fb3ea..bf3e7543 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -74,7 +74,7 @@ pub trait ToPyObject<'p> { /// /// Usage: /// ```let obj: PyObject = ...; -/// let prepared = ::prepare_extract(&obj); +/// let prepared = ::prepare_extract(&obj); /// let extracted = try!(extract(&prepared));``` /// /// Note: depending on the implementation, the lifetime of the extracted result may diff --git a/src/function.rs b/src/function.rs index c3d7409d..ce320c5d 100644 --- a/src/function.rs +++ b/src/function.rs @@ -17,11 +17,11 @@ // DEALINGS IN THE SOFTWARE. use std::ptr; -use python::Python; -use objects::PyObject; +use python::{Python, PythonObject}; +use objects::{PyObject, PyTuple, PyDict, PyString, exc}; use conversion::ToPyObject; use ffi; -use err; +use err::{self, PyResult}; /// Creates a Python callable object that invokes a Rust function. /// @@ -37,14 +37,19 @@ 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) + args: *mut $crate::_detail::ffi::PyObject, + kwargs: *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) { + let kwargs = match $crate::PyObject::from_borrowed_ptr_opt(py, kwargs) { + Some(kwargs) => Some(<$crate::PyDict as $crate::PythonObject>::unchecked_downcast_from(kwargs)), + None => None + }; + match $f(py, &args, kwargs.as_ref()) { Ok(val) => { let obj = $crate::ToPyObject::into_py_object(val, py); return $crate::PythonObject::into_object(obj).steal_ptr(); @@ -58,20 +63,68 @@ macro_rules! py_fn { 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_meth: None, + ml_flags: $crate::_detail::ffi::METH_VARARGS | $crate::_detail::ffi::METH_KEYWORDS, ml_doc: 0 as *const $crate::_detail::libc::c_char }; unsafe { - [ method_def_ $f ].ml_name = - concat!(stringify!($f), "\0").as_ptr() as *const _; + [ method_def_ $f ].ml_name = concat!(stringify!($f), "\0").as_ptr() as *const _; + [ method_def_ $f ].ml_meth = Some( + std::mem::transmute::<$crate::_detail::ffi::PyCFunctionWithKeywords, + $crate::_detail::ffi::PyCFunction>([ wrap_ $f ]) + ); $crate::_detail::py_fn_impl(&mut [ method_def_ $f ]) } - }}) + }}); + ($f: ident ( $( $pname:ident : $ptype:ty ),* ) ) => ( interpolate_idents! {{ + unsafe extern "C" fn [ wrap_ $f ]( + _slf: *mut $crate::_detail::ffi::PyObject, + args: *mut $crate::_detail::ffi::PyObject, + kwargs: *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); + let kwargs = match $crate::PyObject::from_borrowed_ptr_opt(py, kwargs) { + Some(kwargs) => Some(<$crate::PyDict as $crate::PythonObject>::unchecked_downcast_from(kwargs)), + None => None + }; + match py_argparse!(Some(stringify!($f)), &args, kwargs.as_ref(), + ( $($pname : $ptype),* ) { $f( py, $($pname),* ) }) + { + Ok(val) => { + let obj = $crate::ToPyObject::into_py_object(val, py); + return $crate::PythonObject::into_object(obj).steal_ptr(); + } + 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: None, + ml_flags: $crate::_detail::ffi::METH_VARARGS | $crate::_detail::ffi::METH_KEYWORDS, + ml_doc: 0 as *const $crate::_detail::libc::c_char + }; + unsafe { + [ method_def_ $f ].ml_name = concat!(stringify!($f), "\0").as_ptr() as *const _; + [ method_def_ $f ].ml_meth = Some( + std::mem::transmute::<$crate::_detail::ffi::PyCFunctionWithKeywords, + $crate::_detail::ffi::PyCFunction>([ wrap_ $f ]) + ); + $crate::_detail::py_fn_impl(&mut [ method_def_ $f ]) + } + }}); } pub struct PyFn(*mut ffi::PyMethodDef); +#[inline] pub unsafe fn py_fn_impl(def: *mut ffi::PyMethodDef) -> PyFn { PyFn(def) } @@ -86,3 +139,4 @@ impl <'p> ToPyObject<'p> for PyFn { } } + diff --git a/src/lib.rs b/src/lib.rs index 0b5e19df..68dd2a05 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -126,6 +126,7 @@ mod conversion; mod objects; mod objectprotocol; mod pythonrun; +pub mod argparse; mod function; mod rustobject; @@ -168,15 +169,15 @@ pub mod _detail { /// #![feature(plugin)] /// #![plugin(interpolate_idents)] /// #[macro_use] extern crate cpython; -/// use cpython::{Python, PyResult, PyObject, PyTuple}; +/// use cpython::{Python, PyResult, PyObject}; /// /// py_module_initializer!(example, |py, m| { /// try!(m.add("__doc__", "Module documentation string")); -/// try!(m.add("run", py_fn!(run))); +/// try!(m.add("run", py_fn!(run()))); /// Ok(()) /// }); /// -/// fn run<'p>(py: Python<'p>, args: &PyTuple<'p>) -> PyResult<'p, PyObject<'p>> { +/// fn run<'p>(py: Python<'p>) -> PyResult<'p, PyObject<'p>> { /// println!("Rust says: Hello Python!"); /// Ok(py.None()) /// } diff --git a/src/rustobject/method.rs b/src/rustobject/method.rs index 9894f953..722c7320 100644 --- a/src/rustobject/method.rs +++ b/src/rustobject/method.rs @@ -41,9 +41,9 @@ use err; /// PyTuple, PyRustObject, PyRustTypeBuilder}; /// use cpython::{exc}; /// -/// fn mul<'p>(slf: &PyRustObject<'p, i32>, args: &PyTuple<'p>) -> PyResult<'p, i32> { +/// fn mul<'p>(slf: &PyRustObject<'p, i32>, arg: i32) -> PyResult<'p, i32> { /// let py = slf.python(); -/// match slf.get().checked_mul(try!(args.get_item(0).extract::())) { +/// match slf.get().checked_mul(arg) { /// Some(val) => Ok(val), /// None => Err(PyErr::new_lazy_init(py.get_type::(), None)) /// } @@ -52,7 +52,7 @@ use err; /// fn main() { /// let gil = Python::acquire_gil(); /// let multiplier_type = PyRustTypeBuilder::::new(gil.python(), "Multiplier") -/// .add("mul", py_method!(mul)) +/// .add("mul", py_method!(mul(arg: i32))) /// .finish().unwrap(); /// let obj = multiplier_type.create_instance(3, ()).into_object(); /// let result = obj.call_method("mul", &(4,), None).unwrap().extract::().unwrap(); @@ -64,7 +64,8 @@ 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) + args: *mut $crate::_detail::ffi::PyObject, + kwargs: *mut $crate::_detail::ffi::PyObject) -> *mut $crate::_detail::ffi::PyObject { let _guard = $crate::_detail::PanicGuard::with_message("Rust panic in py_method!"); @@ -73,7 +74,11 @@ macro_rules! py_method { 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) { + let kwargs = match $crate::PyObject::from_borrowed_ptr_opt(py, kwargs) { + Some(kwargs) => Some(<$crate::PyDict as $crate::PythonObject>::unchecked_downcast_from(kwargs)), + None => None + }; + match $f(&slf, &args, kwargs.as_ref()) { Ok(val) => { let obj = $crate::ToPyObject::into_py_object(val, py); return $crate::PythonObject::into_object(obj).steal_ptr(); @@ -87,27 +92,134 @@ macro_rules! py_method { 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_meth: None, + ml_flags: $crate::_detail::ffi::METH_VARARGS | $crate::_detail::ffi::METH_KEYWORDS, ml_doc: 0 as *const $crate::_detail::libc::c_char }; unsafe { - [ method_def_ $f ].ml_name = - concat!(stringify!($f), "\0").as_ptr() as *const _; - $crate::_detail::py_method_impl(&mut [ method_def_ $f ], $f) + [ method_def_ $f ].ml_name = concat!(stringify!($f), "\0").as_ptr() as *const _; + [ method_def_ $f ].ml_meth = Some( + std::mem::transmute::<$crate::_detail::ffi::PyCFunctionWithKeywords, + $crate::_detail::ffi::PyCFunction>([ wrap_ $f ]) + ); + $crate::_detail::py_method_impl::py_method_impl(&mut [ method_def_ $f ], $f) + } + }}); + ($f: ident ( $( $pname:ident : $ptype:ty ),* ) ) => ( interpolate_idents! {{ + unsafe extern "C" fn [ wrap_ $f ]( + slf: *mut $crate::_detail::ffi::PyObject, + args: *mut $crate::_detail::ffi::PyObject, + kwargs: *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); + let kwargs = match $crate::PyObject::from_borrowed_ptr_opt(py, kwargs) { + Some(kwargs) => Some(<$crate::PyDict as $crate::PythonObject>::unchecked_downcast_from(kwargs)), + None => None + }; + match py_argparse!(Some(stringify!($f)), &args, kwargs.as_ref(), + ( $($pname : $ptype),* ) { $f( &slf, $($pname),* ) }) + { + Ok(val) => { + let obj = $crate::ToPyObject::into_py_object(val, py); + return $crate::PythonObject::into_object(obj).steal_ptr(); + } + 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: None, + ml_flags: $crate::_detail::ffi::METH_VARARGS | $crate::_detail::ffi::METH_KEYWORDS, + ml_doc: 0 as *const $crate::_detail::libc::c_char + }; + unsafe { + [ method_def_ $f ].ml_name = concat!(stringify!($f), "\0").as_ptr() as *const _; + [ method_def_ $f ].ml_meth = Some( + std::mem::transmute::<$crate::_detail::ffi::PyCFunctionWithKeywords, + $crate::_detail::ffi::PyCFunction>([ wrap_ $f ]) + ); + py_method_call_impl!(&mut [ method_def_ $f ], $f ( $($pname : $ptype),* ) ) } }}) } pub struct MethodDescriptor(*mut ffi::PyMethodDef, marker::PhantomData); -// py_method_impl takes fn(&T) to ensure that the T in MethodDescriptor -// 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 { - MethodDescriptor(def, marker::PhantomData) +#[doc(hidden)] +pub mod py_method_impl { + use ffi; + use err; + use objects::{PyTuple, PyDict}; + use super::MethodDescriptor; + use std::marker; + + // py_method_impl takes fn(&T) to ensure that the T in MethodDescriptor + // 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>, Option<&PyDict<'p>>) -> err::PyResult<'p, R> + ) -> MethodDescriptor { + MethodDescriptor(def, marker::PhantomData) + } + + #[macro_export] + macro_rules! py_method_call_impl { + ( $def:expr, $f:ident ( ) ) + => { $crate::_detail::py_method_impl::py_method_impl_0($def, $f) }; + ( $def:expr, $f:ident ( $n1:ident : $t1:ty ) ) + => { $crate::_detail::py_method_impl::py_method_impl_1($def, $f) }; + ( $def:expr, $f:ident ( $n1:ident : $t1:ty, $n2:ident : $t2:ty ) ) + => { $crate::_detail::py_method_impl::py_method_impl_2($def, $f) }; + ( $def:expr, $f:ident ( $n1:ident : $t1:ty, $n2:ident : $t2:ty, $n3:ident : $t3:ty ) ) + => { $crate::_detail::py_method_impl::py_method_impl_3($def, $f) }; + ( $def:expr, $f:ident ( $n1:ident : $t1:ty, $n2:ident : $t2:ty, $n3:ident : $t3:ty, $n4:ident : $t4:ty ) ) + => { $crate::_detail::py_method_impl::py_method_impl_3($def, $f) }; + } + + pub unsafe fn py_method_impl_0<'p, T, R>( + def: *mut ffi::PyMethodDef, + _f: fn(&T) -> err::PyResult<'p, R> + ) -> MethodDescriptor { + MethodDescriptor(def, marker::PhantomData) + } + + pub unsafe fn py_method_impl_1<'p, T, P1, R>( + def: *mut ffi::PyMethodDef, + _f: fn(&T, P1) -> err::PyResult<'p, R> + ) -> MethodDescriptor { + MethodDescriptor(def, marker::PhantomData) + } + + pub unsafe fn py_method_impl_2<'p, T, P1, P2, R>( + def: *mut ffi::PyMethodDef, + _f: fn(&T, P1, P2) -> err::PyResult<'p, R> + ) -> MethodDescriptor { + MethodDescriptor(def, marker::PhantomData) + } + + pub unsafe fn py_method_impl_3<'p, T, P1, P2, P3, R>( + def: *mut ffi::PyMethodDef, + _f: fn(&T, P1, P2, P3) -> err::PyResult<'p, R> + ) -> MethodDescriptor { + MethodDescriptor(def, marker::PhantomData) + } + + pub unsafe fn py_method_impl_4<'p, T, P1, P2, P3, P4, R>( + def: *mut ffi::PyMethodDef, + _f: fn(&T, P1, P2, P3, P4) -> err::PyResult<'p, R> + ) -> MethodDescriptor { + MethodDescriptor(def, marker::PhantomData) + } } impl <'p, T> TypeMember<'p, T> for MethodDescriptor where T: PythonObject<'p> { @@ -144,16 +256,16 @@ impl <'p, T> TypeMember<'p, T> for MethodDescriptor where T: PythonObject<'p> /// PyTuple, PyType, PyRustTypeBuilder, NoArgs}; /// use cpython::{exc}; /// -/// fn method<'p>(ty: &PyType<'p>, args: &PyTuple<'p>) -> PyResult<'p, i32> { +/// fn method<'p>(py: Python<'p>) -> PyResult<'p, i32> { /// Ok(42) /// } /// /// fn main() { /// let gil = Python::acquire_gil(); /// let my_type = PyRustTypeBuilder::::new(gil.python(), "MyType") -/// .add("method", py_class_method!(method)) +/// .add("method", py_class_method!(method())) /// .finish().unwrap(); -/// let result = my_type.as_object().call_method("method", &NoArgs, None).unwrap(); +/// let result = my_type.as_object().call_method("method", NoArgs, None).unwrap(); /// assert_eq!(42, result.extract::().unwrap()); /// } /// ``` @@ -162,7 +274,8 @@ macro_rules! py_class_method { ($f: ident) => ( interpolate_idents! {{ unsafe extern "C" fn [ wrap_ $f ]( slf: *mut $crate::_detail::ffi::PyObject, - args: *mut $crate::_detail::ffi::PyObject) + args: *mut $crate::_detail::ffi::PyObject, + kwargs: *mut $crate::_detail::ffi::PyObject) -> *mut $crate::_detail::ffi::PyObject { let _guard = $crate::_detail::PanicGuard::with_message("Rust panic in py_method!"); @@ -171,7 +284,11 @@ macro_rules! py_class_method { let slf = <$crate::PyType as $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) { + let kwargs = match $crate::PyObject::from_borrowed_ptr_opt(py, kwargs) { + Some(kwargs) => Some(<$crate::PyDict as $crate::PythonObject>::unchecked_downcast_from(kwargs)), + None => None + }; + match $f(&slf, &args, kwargs.as_ref()) { Ok(val) => { let obj = $crate::ToPyObject::into_py_object(val, py); return $crate::PythonObject::into_object(obj).steal_ptr(); @@ -184,24 +301,76 @@ macro_rules! py_class_method { } static mut [ method_def_ $f ]: $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_ $f ]), - ml_flags: $crate::_detail::ffi::METH_VARARGS | $crate::_detail::ffi::METH_CLASS, + ml_name: 0 as *const $crate::_detail::libc::c_char, + ml_meth: None, + ml_flags: $crate::_detail::ffi::METH_VARARGS + | $crate::_detail::ffi::METH_KEYWORDS + | $crate::_detail::ffi::METH_CLASS, ml_doc: 0 as *const $crate::_detail::libc::c_char }; - unsafe { $crate::_detail::py_class_method_impl(&mut [ method_def_ $f ], $f) } + unsafe { + [ method_def_ $f ].ml_name = concat!(stringify!($f), "\0").as_ptr() as *const _; + [ method_def_ $f ].ml_meth = Some( + std::mem::transmute::<$crate::_detail::ffi::PyCFunctionWithKeywords, + $crate::_detail::ffi::PyCFunction>([ wrap_ $f ]) + ); + $crate::_detail::py_class_method_impl(&mut [ method_def_ $f ]) + } + }}); + ($f: ident ( $( $pname:ident : $ptype:ty ),* ) ) => ( interpolate_idents! {{ + unsafe extern "C" fn [ wrap_ $f ]( + slf: *mut $crate::_detail::ffi::PyObject, + args: *mut $crate::_detail::ffi::PyObject, + kwargs: *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::PyType as $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); + let kwargs = match $crate::PyObject::from_borrowed_ptr_opt(py, kwargs) { + Some(kwargs) => Some(<$crate::PyDict as $crate::PythonObject>::unchecked_downcast_from(kwargs)), + None => None + }; + match py_argparse!(Some(stringify!($f)), &args, kwargs.as_ref(), + ( $($pname : $ptype),* ) { $f( py, $($pname),* ) }) + { + Ok(val) => { + let obj = $crate::ToPyObject::into_py_object(val, py); + return $crate::PythonObject::into_object(obj).steal_ptr(); + } + 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: None, + ml_flags: $crate::_detail::ffi::METH_VARARGS + | $crate::_detail::ffi::METH_KEYWORDS + | $crate::_detail::ffi::METH_CLASS, + ml_doc: 0 as *const $crate::_detail::libc::c_char + }; + unsafe { + [ method_def_ $f ].ml_name = concat!(stringify!($f), "\0").as_ptr() as *const _; + [ method_def_ $f ].ml_meth = Some( + std::mem::transmute::<$crate::_detail::ffi::PyCFunctionWithKeywords, + $crate::_detail::ffi::PyCFunction>([ wrap_ $f ]) + ); + $crate::_detail::py_class_method_impl(&mut [ method_def_ $f ]) + } }}) } pub struct ClassMethodDescriptor(*mut ffi::PyMethodDef); -// py_method_impl takes fn(&T) to ensure that the T in MethodDescriptor -// corresponds to the T in the function signature. -pub unsafe fn py_class_method_impl<'p, R>( - def: *mut ffi::PyMethodDef, - _f: fn(&PyType<'p>, &PyTuple<'p>) -> err::PyResult<'p, R> -) -> ClassMethodDescriptor -{ +#[inline] +pub unsafe fn py_class_method_impl(def: *mut ffi::PyMethodDef) -> ClassMethodDescriptor { ClassMethodDescriptor(def) }