Merge pull request #154 from konstin/capybara
Relax return types and add functions
This commit is contained in:
commit
5717463daf
|
@ -1,3 +1,60 @@
|
|||
# Python Function
|
||||
|
||||
TODO
|
||||
Pyo3 supports two ways to define a function in python. Both require registering
|
||||
the function to a [module](./module.md)
|
||||
|
||||
One way is defining the function in the module definition.
|
||||
|
||||
```rust
|
||||
#![feature(proc_macro)]
|
||||
|
||||
extern crate pyo3;
|
||||
use pyo3::{py, PyResult, Python, PyModule};
|
||||
use pyo3::py::modinit as pymodinit;
|
||||
|
||||
#[pymodinit(rust2py)]
|
||||
fn init_mod(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
|
||||
// Note that the `#[pyfn()]` annotation automatically converts the arguments from
|
||||
// Python objects to Rust values; and the Rust return value back into a Python object.
|
||||
#[pyfn(m, "sum_as_string")]
|
||||
fn sum_as_string_py(_py: Python, a:i64, b:i64) -> PyResult<String> {
|
||||
Ok(format!("{}", a + b).to_string())
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
The other is annotating a function with `#[py::function]` and then adding it
|
||||
to the module using the `add_function_to_module!` macro, which takes the module
|
||||
as first parameter, the function name as second and an instance of `Python`
|
||||
as third.
|
||||
|
||||
```rust
|
||||
#![feature(proc_macro, concat_idents)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate pyo3;
|
||||
use pyo3::{py, PyResult, Python, PyModule};
|
||||
|
||||
use pyo3::py::function as pyfunction;
|
||||
use pyo3::py::modinit as pymodinit;
|
||||
|
||||
#[pyfunction]
|
||||
fn double(x: usize) -> usize {
|
||||
x * 2
|
||||
}
|
||||
|
||||
#[pymodinit(module_with_functions)]
|
||||
fn init_mod(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
m.add_function(wrap_function!(double)).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
||||
use syn;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Argument {
|
||||
VarArgsSeparator,
|
||||
VarArgs(String),
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
||||
|
||||
use syn;
|
||||
use quote::{Tokens, Ident};
|
||||
|
||||
use args::{Argument, parse_arguments};
|
||||
use quote::{Ident, Tokens};
|
||||
use syn;
|
||||
use utils::for_err_msg;
|
||||
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub struct FnArg<'a> {
|
||||
pub name: &'a syn::Ident,
|
||||
pub mode: &'a syn::BindingMode,
|
||||
|
@ -29,6 +28,7 @@ pub enum FnType {
|
|||
FnStatic,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub struct FnSpec<'a> {
|
||||
pub tp: FnType,
|
||||
pub attrs: Vec<Argument>,
|
||||
|
@ -36,8 +36,14 @@ pub struct FnSpec<'a> {
|
|||
pub output: syn::Ty,
|
||||
}
|
||||
|
||||
impl<'a> FnSpec<'a> {
|
||||
pub fn get_return_info(output: &syn::FunctionRetTy) -> syn::Ty {
|
||||
match output {
|
||||
&syn::FunctionRetTy::Default => syn::Ty::Tup(vec![]),
|
||||
&syn::FunctionRetTy::Ty(ref ty) => ty.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FnSpec<'a> {
|
||||
/// Parser function signature and function attributes
|
||||
pub fn parse(name: &'a syn::Ident,
|
||||
sig: &'a syn::MethodSig,
|
||||
|
@ -96,10 +102,7 @@ impl<'a> FnSpec<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
let ty = match sig.decl.output {
|
||||
syn::FunctionRetTy::Default => syn::Ty::Infer,
|
||||
syn::FunctionRetTy::Ty(ref ty) => ty.clone()
|
||||
};
|
||||
let ty = get_return_info(&sig.decl.output);
|
||||
|
||||
FnSpec {
|
||||
tp: fn_type,
|
||||
|
|
|
@ -1,42 +1,14 @@
|
|||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
||||
|
||||
use syn;
|
||||
use quote::Tokens;
|
||||
|
||||
use args;
|
||||
use method;
|
||||
use py_method;
|
||||
use quote::Tokens;
|
||||
use syn;
|
||||
use utils;
|
||||
|
||||
|
||||
pub fn build_py3_module_init(ast: &mut syn::Item, attr: String) -> Tokens {
|
||||
let modname = &attr[1..attr.len()-1].to_string();
|
||||
|
||||
match ast.node {
|
||||
syn::ItemKind::Fn(_, _, _, _, _, ref mut block) => {
|
||||
let mut stmts = Vec::new();
|
||||
for stmt in block.stmts.iter_mut() {
|
||||
match stmt {
|
||||
&mut syn::Stmt::Item(ref mut item) => {
|
||||
if let Some(block) = wrap_fn(item) {
|
||||
for stmt in block.stmts.iter() {
|
||||
stmts.push(stmt.clone());
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
stmts.push(stmt.clone());
|
||||
}
|
||||
block.stmts = stmts;
|
||||
|
||||
py3_init(&ast.ident, &modname, utils::get_doc(&ast.attrs, false))
|
||||
},
|
||||
_ => panic!("#[modinit] can only be used with fn block"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates the function that is called by the python interpreter to initialize the native
|
||||
/// module
|
||||
pub fn py3_init(fnname: &syn::Ident, name: &String, doc: syn::Lit) -> Tokens {
|
||||
let m_name = syn::Ident::from(name.trim().as_ref());
|
||||
let cb_name = syn::Ident::from(format!("PyInit_{}", name.trim()).as_ref());
|
||||
|
@ -84,34 +56,6 @@ pub fn py3_init(fnname: &syn::Ident, name: &String, doc: syn::Lit) -> Tokens {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn build_py2_module_init(ast: &mut syn::Item, attr: String) -> Tokens {
|
||||
let modname = &attr[1..attr.len()-1].to_string();
|
||||
|
||||
match ast.node {
|
||||
syn::ItemKind::Fn(_, _, _, _, _, ref mut block) => {
|
||||
let mut stmts = Vec::new();
|
||||
for stmt in block.stmts.iter_mut() {
|
||||
match stmt {
|
||||
&mut syn::Stmt::Item(ref mut item) => {
|
||||
if let Some(block) = wrap_fn(item) {
|
||||
for stmt in block.stmts.iter() {
|
||||
stmts.push(stmt.clone());
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
stmts.push(stmt.clone());
|
||||
}
|
||||
block.stmts = stmts;
|
||||
|
||||
py2_init(&ast.ident, &modname, utils::get_doc(&ast.attrs, false))
|
||||
},
|
||||
_ => panic!("#[modinit] can only be used with fn block"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn py2_init(fnname: &syn::Ident, name: &String, doc: syn::Lit) -> Tokens {
|
||||
let m_name = syn::Ident::from(name.trim().as_ref());
|
||||
let cb_name = syn::Ident::from(format!("init{}", name.trim()).as_ref());
|
||||
|
@ -150,170 +94,210 @@ pub fn py2_init(fnname: &syn::Ident, name: &String, doc: syn::Lit) -> Tokens {
|
|||
}
|
||||
}
|
||||
|
||||
fn wrap_fn(item: &mut syn::Item) -> Option<Box<syn::Block>> {
|
||||
let name = item.ident.clone();
|
||||
/// Finds and takes care of the #[pyfn(...)] in #[modinit(...)]
|
||||
pub fn process_functions_in_module(ast: &mut syn::Item) {
|
||||
if let syn::ItemKind::Fn(_, _, _, _, _, ref mut block) = ast.node {
|
||||
let mut stmts: Vec<syn::Stmt> = Vec::new();
|
||||
for stmt in block.stmts.iter_mut() {
|
||||
if let &mut syn::Stmt::Item(ref mut item) = stmt {
|
||||
if let Some((module_name, python_name, pyfn_attrs)) =
|
||||
extract_pyfn_attrs(&mut item.attrs)
|
||||
{
|
||||
let function_to_python = add_fn_to_module(item, &python_name, pyfn_attrs);
|
||||
let function_wrapper_ident = function_wrapper_ident(&item.ident);
|
||||
let tokens = quote! {
|
||||
fn block_wrapper() {
|
||||
#function_to_python
|
||||
|
||||
#module_name.add_function(&#function_wrapper_ident);
|
||||
}
|
||||
}.to_string();
|
||||
|
||||
let item = syn::parse_item(tokens.as_str()).unwrap();
|
||||
let block = match item.node {
|
||||
syn::ItemKind::Fn(_, _, _, _, _, ref block) => block.clone(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
stmts.extend(block.stmts.into_iter());
|
||||
}
|
||||
};
|
||||
stmts.push(stmt.clone());
|
||||
}
|
||||
block.stmts = stmts;
|
||||
} else {
|
||||
panic!("#[modinit] can only be used with fn block");
|
||||
}
|
||||
}
|
||||
|
||||
/// Transforms a rust fn arg parsed with syn into a method::FnArg
|
||||
fn wrap_fn_argument<'a>(input: &'a syn::FnArg, name: &'a syn::Ident) -> Option<method::FnArg<'a>> {
|
||||
match input {
|
||||
&syn::FnArg::SelfRef(_, _) | &syn::FnArg::SelfValue(_) => None,
|
||||
&syn::FnArg::Captured(ref pat, ref ty) => {
|
||||
let (mode, ident) = match pat {
|
||||
&syn::Pat::Ident(ref mode, ref ident, _) => (mode, ident),
|
||||
_ => panic!("unsupported argument: {:?}", pat),
|
||||
};
|
||||
|
||||
let py = match ty {
|
||||
&syn::Ty::Path(_, ref path) => if let Some(segment) = path.segments.last() {
|
||||
segment.ident.as_ref() == "Python"
|
||||
} else {
|
||||
false
|
||||
},
|
||||
_ => false,
|
||||
};
|
||||
|
||||
let opt = method::check_arg_ty_and_optional(&name, ty);
|
||||
Some(method::FnArg {
|
||||
name: ident,
|
||||
mode,
|
||||
ty,
|
||||
optional: opt,
|
||||
py,
|
||||
reference: method::is_ref(&name, ty),
|
||||
})
|
||||
}
|
||||
&syn::FnArg::Ignored(_) => panic!("ignored argument: {:?}", name),
|
||||
}
|
||||
}
|
||||
|
||||
/// Extracts the data from the #[pyfn(...)] attribute of a function
|
||||
fn extract_pyfn_attrs(
|
||||
attrs: &mut Vec<syn::Attribute>,
|
||||
) -> Option<(syn::Ident, syn::Ident, Vec<args::Argument>)> {
|
||||
let mut new_attrs = Vec::new();
|
||||
let mut fnname = None;
|
||||
let mut modname = None;
|
||||
let mut fn_attrs = Vec::new();
|
||||
|
||||
for attr in item.attrs.iter() {
|
||||
match attr.value {
|
||||
syn::MetaItem::List(ref name, ref meta) => {
|
||||
match name.as_ref() {
|
||||
"pyfn" => {
|
||||
if meta.len() >= 2 {
|
||||
match meta[0] {
|
||||
syn::NestedMetaItem::MetaItem(syn::MetaItem::Word(ref ident)) => {
|
||||
modname = Some(ident.clone());
|
||||
}
|
||||
_ => modname = None
|
||||
}
|
||||
match meta[1] {
|
||||
syn::NestedMetaItem::Literal(syn::Lit::Str(ref s, _)) => {
|
||||
fnname = Some(syn::Ident::from(s.as_str()));
|
||||
}
|
||||
_ => fnname = None
|
||||
}
|
||||
} else {
|
||||
println!("can not parse 'pyfn' params {:?}", attr);
|
||||
modname = None
|
||||
for attr in attrs.iter() {
|
||||
if let syn::MetaItem::List(ref name, ref meta) = attr.value {
|
||||
if name.as_ref() == "pyfn" {
|
||||
if meta.len() >= 2 {
|
||||
match meta[0] {
|
||||
syn::NestedMetaItem::MetaItem(syn::MetaItem::Word(ref ident)) => {
|
||||
modname = Some(ident.clone());
|
||||
}
|
||||
if meta.len() >= 3 {
|
||||
fn_attrs = args::parse_arguments(&meta[2..meta.len()]);
|
||||
}
|
||||
continue;
|
||||
_ => panic!("The first parameter of pyfn must be a MetaItem"),
|
||||
}
|
||||
_ => (),
|
||||
match meta[1] {
|
||||
syn::NestedMetaItem::Literal(syn::Lit::Str(ref s, _)) => {
|
||||
fnname = Some(syn::Ident::from(s.as_str()));
|
||||
}
|
||||
_ => panic!("The second parameter of pyfn must be a Literal"),
|
||||
}
|
||||
if meta.len() >= 3 {
|
||||
fn_attrs = args::parse_arguments(&meta[2..meta.len()]);
|
||||
}
|
||||
} else {
|
||||
panic!("can not parse 'pyfn' params {:?}", attr);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
new_attrs.push(attr.clone())
|
||||
}
|
||||
item.attrs.clear();
|
||||
item.attrs.extend(new_attrs);
|
||||
|
||||
if let None = fnname {
|
||||
return None
|
||||
}
|
||||
if let None = modname {
|
||||
return None
|
||||
}
|
||||
|
||||
match item.node {
|
||||
syn::ItemKind::Fn(ref decl, _, _, _, _, _) => {
|
||||
let mut arguments = Vec::new();
|
||||
|
||||
for input in decl.inputs.iter() {
|
||||
match input {
|
||||
&syn::FnArg::SelfRef(_, _) | &syn::FnArg::SelfValue(_) => (),
|
||||
&syn::FnArg::Captured(ref pat, ref ty) => {
|
||||
let (mode, ident) = match pat {
|
||||
&syn::Pat::Ident(ref mode, ref ident, _) =>
|
||||
(mode, ident),
|
||||
_ =>
|
||||
panic!("unsupported argument: {:?}", pat),
|
||||
};
|
||||
|
||||
let py = match ty {
|
||||
&syn::Ty::Path(_, ref path) =>
|
||||
if let Some(segment) = path.segments.last() {
|
||||
segment.ident.as_ref() == "Python"
|
||||
} else {
|
||||
false
|
||||
},
|
||||
_ => false
|
||||
};
|
||||
|
||||
let opt = method::check_arg_ty_and_optional(&name, ty);
|
||||
arguments.push(method::FnArg {name: ident,
|
||||
mode: mode,
|
||||
ty: ty,
|
||||
optional: opt,
|
||||
py: py,
|
||||
reference: method::is_ref(&name, ty)});
|
||||
}
|
||||
&syn::FnArg::Ignored(_) =>
|
||||
panic!("ignored argument: {:?}", name),
|
||||
}
|
||||
}
|
||||
|
||||
let ty = match decl.output {
|
||||
syn::FunctionRetTy::Default => syn::Ty::Infer,
|
||||
syn::FunctionRetTy::Ty(ref ty) => ty.clone()
|
||||
};
|
||||
|
||||
let spec = method::FnSpec {
|
||||
tp: method::FnType::Fn,
|
||||
attrs: fn_attrs,
|
||||
args: arguments,
|
||||
output: ty,
|
||||
};
|
||||
|
||||
let m = modname.unwrap();
|
||||
let fnname = fnname.unwrap();
|
||||
let wrapper = impl_wrap(&name, &spec);
|
||||
let item2 = item.clone();
|
||||
let doc = utils::get_doc(&item.attrs, true);
|
||||
|
||||
let tokens = quote! {
|
||||
fn test() {
|
||||
#item2
|
||||
|
||||
#[allow(unused_imports)]
|
||||
{
|
||||
use std;
|
||||
use pyo3 as _pyo3;
|
||||
use pyo3::ObjectProtocol;
|
||||
|
||||
#wrapper
|
||||
|
||||
let _def = pyo3::class::PyMethodDef {
|
||||
ml_name: stringify!(#fnname),
|
||||
ml_meth: pyo3::class::PyMethodType::PyCFunctionWithKeywords(__wrap),
|
||||
ml_flags: pyo3::ffi::METH_VARARGS | pyo3::ffi::METH_KEYWORDS,
|
||||
ml_doc: #doc,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let func = pyo3::PyObject::from_owned_ptr_or_panic(
|
||||
py, pyo3::ffi::PyCFunction_New(
|
||||
Box::into_raw(Box::new(_def.as_method_def())),
|
||||
std::ptr::null_mut()));
|
||||
|
||||
#m.add(stringify!(#fnname), func)?
|
||||
}
|
||||
}
|
||||
}
|
||||
}.to_string();
|
||||
|
||||
let item = syn::parse_item(tokens.as_str()).unwrap();
|
||||
match item.node {
|
||||
syn::ItemKind::Fn(_, _, _, _, _, ref block) => {
|
||||
return Some(block.clone())
|
||||
},
|
||||
_ => ()
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
|
||||
None
|
||||
attrs.clear();
|
||||
attrs.extend(new_attrs);
|
||||
Some((modname?, fnname?, fn_attrs))
|
||||
}
|
||||
|
||||
/// Coordinates the naming of a the add-function-to-python-module function
|
||||
fn function_wrapper_ident(name: &syn::Ident) -> syn::Ident {
|
||||
// Make sure this ident matches the one of wrap_function
|
||||
syn::Ident::new("__pyo3_get_function_".to_string() + &name.to_string())
|
||||
}
|
||||
|
||||
/// Generate static method wrapper (PyCFunction, PyCFunctionWithKeywords)
|
||||
pub fn impl_wrap(name: &syn::Ident, spec: &method::FnSpec) -> Tokens {
|
||||
let names: Vec<syn::Ident> = spec.args.iter().enumerate().map(
|
||||
|item| if item.1.py {syn::Ident::from("_py")} else {
|
||||
syn::Ident::from(format!("arg{}", item.0))}).collect();
|
||||
let cb = quote! {{
|
||||
#name(#(#names),*)
|
||||
}};
|
||||
/// Generates python wrapper over a function that allows adding it to a python module as a python
|
||||
/// function
|
||||
pub fn add_fn_to_module(
|
||||
item: &mut syn::Item,
|
||||
python_name: &syn::Ident,
|
||||
pyfn_attrs: Vec<args::Argument>,
|
||||
) -> Tokens {
|
||||
let name = item.ident.clone();
|
||||
|
||||
let decl = if let syn::ItemKind::Fn(ref decl, _, _, _, _, _) = item.node {
|
||||
decl.clone()
|
||||
} else {
|
||||
panic!("Expected a function")
|
||||
};
|
||||
|
||||
let mut arguments = Vec::new();
|
||||
|
||||
for input in decl.inputs.iter() {
|
||||
if let Some(fn_arg) = wrap_fn_argument(input, &name) {
|
||||
arguments.push(fn_arg);
|
||||
}
|
||||
}
|
||||
|
||||
let ty = method::get_return_info(&decl.output);
|
||||
|
||||
let spec = method::FnSpec {
|
||||
tp: method::FnType::Fn,
|
||||
attrs: pyfn_attrs,
|
||||
args: arguments,
|
||||
output: ty,
|
||||
};
|
||||
|
||||
let function_wrapper_ident = function_wrapper_ident(&name);
|
||||
|
||||
let wrapper = function_c_wrapper(&name, &spec);
|
||||
let doc = utils::get_doc(&item.attrs, true);
|
||||
|
||||
let tokens = quote! (
|
||||
fn #function_wrapper_ident(py: ::pyo3::Python) -> ::pyo3::PyObject {
|
||||
use std;
|
||||
use pyo3 as _pyo3;
|
||||
use pyo3::ObjectProtocol;
|
||||
|
||||
#wrapper
|
||||
|
||||
let _def = pyo3::class::PyMethodDef {
|
||||
ml_name: stringify!(#python_name),
|
||||
ml_meth: pyo3::class::PyMethodType::PyCFunctionWithKeywords(__wrap),
|
||||
ml_flags: pyo3::ffi::METH_VARARGS | pyo3::ffi::METH_KEYWORDS,
|
||||
ml_doc: #doc,
|
||||
};
|
||||
|
||||
let function = unsafe {
|
||||
pyo3::PyObject::from_owned_ptr_or_panic(
|
||||
py,
|
||||
pyo3::ffi::PyCFunction_New(
|
||||
Box::into_raw(Box::new(_def.as_method_def())),
|
||||
std::ptr::null_mut()
|
||||
)
|
||||
)
|
||||
};
|
||||
|
||||
function
|
||||
}
|
||||
);
|
||||
|
||||
tokens
|
||||
}
|
||||
|
||||
/// Generate static function wrapper (PyCFunction, PyCFunctionWithKeywords)
|
||||
fn function_c_wrapper(name: &syn::Ident, spec: &method::FnSpec) -> Tokens {
|
||||
let names: Vec<syn::Ident> = spec.args
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|item| {
|
||||
if item.1.py {
|
||||
syn::Ident::from("_py")
|
||||
} else {
|
||||
syn::Ident::from(format!("arg{}", item.0))
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let cb = quote! {
|
||||
::pyo3::ReturnTypeIntoPyResult::return_type_into_py_result(#name(#(#names),*))
|
||||
};
|
||||
|
||||
let body = py_method::impl_arg_params(spec, cb);
|
||||
let output = &spec.output;
|
||||
let body_to_result = py_method::body_to_result(&body, spec);
|
||||
|
||||
quote! {
|
||||
#[allow(unused_variables, unused_imports)]
|
||||
|
@ -329,9 +313,7 @@ pub fn impl_wrap(name: &syn::Ident, spec: &method::FnSpec) -> Tokens {
|
|||
let _args = _py.from_borrowed_ptr::<_pyo3::PyTuple>(_args);
|
||||
let _kwargs = _pyo3::argparse::get_kwargs(_py, _kwargs);
|
||||
|
||||
let _result: #output = {
|
||||
#body
|
||||
};
|
||||
#body_to_result
|
||||
_pyo3::callback::cb_convert(
|
||||
_pyo3::callback::PyObjectCallbackConverter, _py, _result)
|
||||
}
|
||||
|
|
|
@ -268,11 +268,13 @@ fn impl_class(cls: &syn::Ident, base: &syn::Ident,
|
|||
let gil = _pyo3::Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
|
||||
let error_message = "An error occurred while initializing class ".to_string() +
|
||||
<#cls as _pyo3::typeob::PyTypeInfo>::NAME.as_ref();
|
||||
|
||||
// automatically initialize the class on-demand
|
||||
_pyo3::typeob::initialize_type::<#cls>(py, None)
|
||||
.map_err(|e| e.print(py))
|
||||
.expect(format!("An error occurred while initializing class {}",
|
||||
<#cls as _pyo3::typeob::PyTypeInfo>::NAME).as_ref());
|
||||
.expect(&error_message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -337,7 +339,7 @@ fn impl_descriptors(cls: &syn::Ty, descriptors: Vec<(syn::Field, Vec<FnType>)>)
|
|||
py: true,
|
||||
reference: false
|
||||
}],
|
||||
output: syn::parse::ty("PyResult<()>").expect("error parse PyResult<()>")
|
||||
output: syn::parse::ty("PyResult<()>").expect("error parse PyResult<()>"),
|
||||
};
|
||||
impl_py_setter_def(&name, doc, setter, &impl_wrap_setter(&Box::new(cls.clone()), &setter_name, &spec))
|
||||
},
|
||||
|
|
|
@ -19,7 +19,7 @@ pub fn build_py_methods(ast: &mut syn::Item) -> Tokens {
|
|||
}
|
||||
}
|
||||
|
||||
fn impl_methods(ty: &Box<syn::Ty>, impls: &mut Vec<syn::ImplItem>) -> Tokens {
|
||||
pub fn impl_methods(ty: &Box<syn::Ty>, impls: &mut Vec<syn::ImplItem>) -> Tokens {
|
||||
|
||||
// get method names in impl block
|
||||
let mut methods = Vec::new();
|
||||
|
|
|
@ -43,12 +43,23 @@ fn check_generic(name: &syn::Ident, sig: &syn::MethodSig) {
|
|||
}
|
||||
|
||||
|
||||
pub fn body_to_result(body: &Tokens, spec: &FnSpec) -> Tokens {
|
||||
let output = &spec.output;
|
||||
quote! {
|
||||
use pyo3::ReturnTypeIntoPyResult;
|
||||
let _result: PyResult<<#output as ReturnTypeIntoPyResult>::Inner> = {
|
||||
#body
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate function wrapper (PyCFunction, PyCFunctionWithKeywords)
|
||||
pub fn impl_wrap(cls: &Box<syn::Ty>, name: &syn::Ident, spec: &FnSpec, noargs: bool) -> Tokens {
|
||||
let cb = impl_call(cls, name, &spec);
|
||||
let output = &spec.output;
|
||||
let body = impl_call(cls, name, &spec);
|
||||
|
||||
if spec.args.is_empty() && noargs {
|
||||
let body_to_result = body_to_result(&body, spec);
|
||||
|
||||
quote! {
|
||||
unsafe extern "C" fn __wrap(
|
||||
_slf: *mut _pyo3::ffi::PyObject) -> *mut _pyo3::ffi::PyObject
|
||||
|
@ -59,15 +70,14 @@ pub fn impl_wrap(cls: &Box<syn::Ty>, name: &syn::Ident, spec: &FnSpec, noargs: b
|
|||
let _py = _pyo3::Python::assume_gil_acquired();
|
||||
let _slf = _py.mut_from_borrowed_ptr::<#cls>(_slf);
|
||||
|
||||
let _result: #output = {
|
||||
#cb
|
||||
};
|
||||
#body_to_result
|
||||
_pyo3::callback::cb_convert(
|
||||
_pyo3::callback::PyObjectCallbackConverter, _py, _result)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let body = impl_arg_params(&spec, cb);
|
||||
let body = impl_arg_params(&spec, body);
|
||||
let body_to_result = body_to_result(&body, spec);
|
||||
|
||||
quote! {
|
||||
unsafe extern "C" fn __wrap(
|
||||
|
@ -84,9 +94,7 @@ pub fn impl_wrap(cls: &Box<syn::Ty>, name: &syn::Ident, spec: &FnSpec, noargs: b
|
|||
let _args = _py.from_borrowed_ptr::<_pyo3::PyTuple>(_args);
|
||||
let _kwargs = _pyo3::argparse::get_kwargs(_py, _kwargs);
|
||||
|
||||
let _result: #output = {
|
||||
#body
|
||||
};
|
||||
#body_to_result
|
||||
_pyo3::callback::cb_convert(
|
||||
_pyo3::callback::PyObjectCallbackConverter, _py, _result)
|
||||
}
|
||||
|
@ -127,12 +135,12 @@ pub fn impl_wrap_new(cls: &Box<syn::Ty>, name: &syn::Ident, spec: &FnSpec) -> To
|
|||
let names: Vec<syn::Ident> = spec.args.iter().enumerate().map(
|
||||
|item| if item.1.py {syn::Ident::from("_py")} else {
|
||||
syn::Ident::from(format!("arg{}", item.0))}).collect();
|
||||
let cb = quote! {{
|
||||
#cls::#name(&_obj, #(#names),*)
|
||||
}};
|
||||
let cb = quote! {
|
||||
::pyo3::ReturnTypeIntoPyResult::return_type_into_py_result(#cls::#name(&_obj, #(#names),*))
|
||||
};
|
||||
|
||||
let body = impl_arg_params(spec, cb);
|
||||
let output = &spec.output;
|
||||
let body_to_result = body_to_result(&body, spec);
|
||||
|
||||
quote! {
|
||||
#[allow(unused_mut)]
|
||||
|
@ -152,9 +160,7 @@ pub fn impl_wrap_new(cls: &Box<syn::Ty>, name: &syn::Ident, spec: &FnSpec) -> To
|
|||
let _args = _py.from_borrowed_ptr::<_pyo3::PyTuple>(_args);
|
||||
let _kwargs = _pyo3::argparse::get_kwargs(_py, _kwargs);
|
||||
|
||||
let _result: #output = {
|
||||
#body
|
||||
};
|
||||
#body_to_result
|
||||
|
||||
match _result {
|
||||
Ok(_) => _obj.into_ptr(),
|
||||
|
@ -176,7 +182,13 @@ pub fn impl_wrap_new(cls: &Box<syn::Ty>, name: &syn::Ident, spec: &FnSpec) -> To
|
|||
/// Generate function wrapper for ffi::initproc
|
||||
fn impl_wrap_init(cls: &Box<syn::Ty>, name: &syn::Ident, spec: &FnSpec) -> Tokens {
|
||||
let cb = impl_call(cls, name, &spec);
|
||||
let output = &spec.output;
|
||||
if quote! {#output} != quote! {PyResult<()>} || quote! {#output} != quote! {()}{
|
||||
panic!("Constructor must return PyResult<()> or a ()");
|
||||
}
|
||||
|
||||
let body = impl_arg_params(&spec, cb);
|
||||
let body_to_result = body_to_result(&body, spec);
|
||||
|
||||
quote! {
|
||||
#[allow(unused_mut)]
|
||||
|
@ -192,9 +204,7 @@ fn impl_wrap_init(cls: &Box<syn::Ty>, name: &syn::Ident, spec: &FnSpec) -> Token
|
|||
let _args = _py.from_borrowed_ptr::<_pyo3::PyTuple>(_args);
|
||||
let _kwargs = _pyo3::argparse::get_kwargs(_py, _kwargs);
|
||||
|
||||
let _result: PyResult<()> = {
|
||||
#body
|
||||
};
|
||||
#body_to_result
|
||||
match _result {
|
||||
Ok(_) => 0,
|
||||
Err(e) => {
|
||||
|
@ -211,11 +221,12 @@ pub fn impl_wrap_class(cls: &Box<syn::Ty>, name: &syn::Ident, spec: &FnSpec) ->
|
|||
let names: Vec<syn::Ident> = spec.args.iter().enumerate().map(
|
||||
|item| if item.1.py {syn::Ident::from("_py")} else {
|
||||
syn::Ident::from(format!("arg{}", item.0))}).collect();
|
||||
let cb = quote! {{
|
||||
#cls::#name(&_cls, #(#names),*)
|
||||
}};
|
||||
let cb = quote! {
|
||||
::pyo3::ReturnTypeIntoPyResult::return_type_into_py_result(#cls::#name(&_cls, #(#names),*))
|
||||
};
|
||||
|
||||
let body = impl_arg_params(spec, cb);
|
||||
let output = &spec.output;
|
||||
let body_to_result = body_to_result(&body, spec);
|
||||
|
||||
quote! {
|
||||
#[allow(unused_mut)]
|
||||
|
@ -231,9 +242,7 @@ pub fn impl_wrap_class(cls: &Box<syn::Ty>, name: &syn::Ident, spec: &FnSpec) ->
|
|||
let _args = _py.from_borrowed_ptr::<_pyo3::PyTuple>(_args);
|
||||
let _kwargs = _pyo3::argparse::get_kwargs(_py, _kwargs);
|
||||
|
||||
let _result: #output = {
|
||||
#body
|
||||
};
|
||||
#body_to_result
|
||||
_pyo3::callback::cb_convert(
|
||||
_pyo3::callback::PyObjectCallbackConverter, _py, _result)
|
||||
}
|
||||
|
@ -245,12 +254,12 @@ pub fn impl_wrap_static(cls: &Box<syn::Ty>, name: &syn::Ident, spec: &FnSpec) ->
|
|||
let names: Vec<syn::Ident> = spec.args.iter().enumerate().map(
|
||||
|item| if item.1.py {syn::Ident::from("_py")} else {
|
||||
syn::Ident::from(format!("arg{}", item.0))}).collect();
|
||||
let cb = quote! {{
|
||||
#cls::#name(#(#names),*)
|
||||
}};
|
||||
let cb = quote! {
|
||||
::pyo3::ReturnTypeIntoPyResult::return_type_into_py_result(#cls::#name(#(#names),*))
|
||||
};
|
||||
|
||||
let body = impl_arg_params(spec, cb);
|
||||
let output = &spec.output;
|
||||
let body_to_result = body_to_result(&body, spec);
|
||||
|
||||
quote! {
|
||||
#[allow(unused_mut)]
|
||||
|
@ -265,9 +274,7 @@ pub fn impl_wrap_static(cls: &Box<syn::Ty>, name: &syn::Ident, spec: &FnSpec) ->
|
|||
let _args = _py.from_borrowed_ptr::<_pyo3::PyTuple>(_args);
|
||||
let _kwargs = _pyo3::argparse::get_kwargs(_py, _kwargs);
|
||||
|
||||
let _result: #output = {
|
||||
#body
|
||||
};
|
||||
#body_to_result
|
||||
_pyo3::callback::cb_convert(
|
||||
_pyo3::callback::PyObjectCallbackConverter, _py, _result)
|
||||
}
|
||||
|
@ -343,9 +350,9 @@ fn impl_call(_cls: &Box<syn::Ty>, fname: &syn::Ident, spec: &FnSpec) -> Tokens {
|
|||
syn::Ident::from(format!("arg{}", item.0))
|
||||
}
|
||||
).collect();
|
||||
quote! {{
|
||||
_slf.#fname(#(#names),*)
|
||||
}}
|
||||
quote! {
|
||||
::pyo3::ReturnTypeIntoPyResult::return_type_into_py_result(_slf.#fname(#(#names),*))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn impl_arg_params(spec: &FnSpec, body: Tokens) -> Tokens {
|
||||
|
@ -393,6 +400,7 @@ pub fn impl_arg_params(spec: &FnSpec, body: Tokens) -> Tokens {
|
|||
let mut rargs = spec.args.clone();
|
||||
rargs.reverse();
|
||||
let mut body = body;
|
||||
|
||||
for (idx, arg) in rargs.iter().enumerate() {
|
||||
body = impl_arg_param(&arg, &spec, &body, len-idx-1);
|
||||
}
|
||||
|
@ -424,7 +432,7 @@ pub fn impl_arg_params(spec: &FnSpec, body: Tokens) -> Tokens {
|
|||
|
||||
fn impl_arg_param(arg: &FnArg, spec: &FnSpec, body: &Tokens, idx: usize) -> Tokens {
|
||||
if arg.py {
|
||||
return body.clone()
|
||||
return body.clone();
|
||||
}
|
||||
let ty = arg.ty;
|
||||
let name = arg.name;
|
||||
|
@ -436,27 +444,22 @@ fn impl_arg_param(arg: &FnArg, spec: &FnSpec, body: &Tokens, idx: usize) -> Toke
|
|||
|
||||
if spec.is_args(&name) {
|
||||
quote! {
|
||||
match <#ty as _pyo3::FromPyObject>::extract(_args.as_ref())
|
||||
{
|
||||
Ok(#arg_name) => {
|
||||
#body
|
||||
}
|
||||
Err(e) => Err(e)
|
||||
}
|
||||
<#ty as _pyo3::FromPyObject>::extract(_args.as_ref())
|
||||
.and_then(|#arg_name| {
|
||||
#body
|
||||
})
|
||||
}
|
||||
}
|
||||
else if spec.is_kwargs(&name) {
|
||||
} else if spec.is_kwargs(&name) {
|
||||
quote! {{
|
||||
let #arg_name = _kwargs;
|
||||
#body
|
||||
}}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
if let Some(_) = arg.optional {
|
||||
// default value
|
||||
let mut default = Tokens::new();
|
||||
if let Some(d) = spec.default_value(name) {
|
||||
let dt = quote!{ Some(#d) };
|
||||
let dt = quote! { Some(#d) };
|
||||
dt.to_tokens(&mut default);
|
||||
} else {
|
||||
syn::Ident::from("None").to_tokens(&mut default);
|
||||
|
@ -500,15 +503,12 @@ fn impl_arg_param(arg: &FnArg, spec: &FnSpec, body: &Tokens, idx: usize) -> Toke
|
|||
Err(e) => Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
quote! {
|
||||
match _iter.next().unwrap().as_ref().unwrap().extract() {
|
||||
Ok(#arg_name) => {
|
||||
_iter.next().unwrap().as_ref().unwrap().extract()
|
||||
.and_then(|#arg_name| {
|
||||
#body
|
||||
}
|
||||
Err(e) => Err(e)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +1,17 @@
|
|||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
||||
|
||||
#![recursion_limit="1024"]
|
||||
#![recursion_limit = "1024"]
|
||||
#![feature(proc_macro)]
|
||||
|
||||
extern crate proc_macro;
|
||||
extern crate syn;
|
||||
extern crate quote;
|
||||
extern crate pyo3_derive_backend;
|
||||
extern crate quote;
|
||||
extern crate syn;
|
||||
|
||||
use std::str::FromStr;
|
||||
use proc_macro::TokenStream;
|
||||
|
||||
use quote::{Tokens, ToTokens};
|
||||
use pyo3_derive_backend::*;
|
||||
|
||||
use quote::{ToTokens, Tokens};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn mod2init(attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||
|
@ -24,7 +22,13 @@ pub fn mod2init(attr: TokenStream, input: TokenStream) -> TokenStream {
|
|||
let mut ast = syn::parse_item(&source).unwrap();
|
||||
|
||||
// Build the output
|
||||
let init = module::build_py2_module_init(&mut ast, utils::attr_with_parentheses(attr));
|
||||
module::process_functions_in_module(&mut ast);
|
||||
|
||||
let attr = utils::attr_with_parentheses(attr);
|
||||
|
||||
let modname = &attr[1..attr.len() - 1].to_string();
|
||||
|
||||
let init = module::py2_init(&ast.ident, &modname, utils::get_doc(&ast.attrs, false));
|
||||
|
||||
// Return the generated impl as a TokenStream
|
||||
let mut tokens = Tokens::new();
|
||||
|
@ -43,7 +47,13 @@ pub fn mod3init(attr: TokenStream, input: TokenStream) -> TokenStream {
|
|||
let mut ast = syn::parse_item(&source).unwrap();
|
||||
|
||||
// Build the output
|
||||
let init = module::build_py3_module_init(&mut ast, utils::attr_with_parentheses(attr));
|
||||
module::process_functions_in_module(&mut ast);
|
||||
|
||||
let attr = utils::attr_with_parentheses(attr);
|
||||
|
||||
let modname = &attr[1..attr.len() - 1].to_string();
|
||||
|
||||
let init = module::py3_init(&ast.ident, &modname, utils::get_doc(&ast.attrs, false));
|
||||
|
||||
// Return the generated impl as a TokenStream
|
||||
let mut tokens = Tokens::new();
|
||||
|
@ -109,3 +119,23 @@ pub fn methods(_: TokenStream, input: TokenStream) -> TokenStream {
|
|||
|
||||
TokenStream::from_str(s.as_str()).unwrap()
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn function(_: TokenStream, input: TokenStream) -> TokenStream {
|
||||
// Construct a string representation of the type definition
|
||||
let source = input.to_string();
|
||||
|
||||
// Parse the string representation into a syntax tree
|
||||
let mut ast = syn::parse_item(&source).unwrap();
|
||||
|
||||
// Build the output
|
||||
let python_name = ast.ident.clone();
|
||||
let expanded = module::add_fn_to_module(&mut ast, &python_name, Vec::new());
|
||||
|
||||
// Return the generated impl as a TokenStream
|
||||
let mut tokens = Tokens::new();
|
||||
ast.to_tokens(&mut tokens);
|
||||
let s = String::from(tokens.as_str()) + expanded.as_str();
|
||||
|
||||
TokenStream::from_str(s.as_str()).unwrap()
|
||||
}
|
||||
|
|
|
@ -301,3 +301,31 @@ impl<T> PyTryFrom for T where T: PyTypeInfo {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// This trait wraps a T: IntoPyObject into PyResult<T> while PyResult<T> remains PyResult<T>.
|
||||
///
|
||||
/// This is necessaty because proc macros run before typechecking and can't decide
|
||||
/// whether a return type is a (possibly aliased) PyResult or not. It is also quite handy because
|
||||
/// the codegen is currently built on the assumption that all functions return a PyResult.
|
||||
pub trait ReturnTypeIntoPyResult {
|
||||
type Inner;
|
||||
|
||||
fn return_type_into_py_result(self) -> PyResult<Self::Inner>;
|
||||
}
|
||||
|
||||
impl<T: IntoPyObject> ReturnTypeIntoPyResult for T {
|
||||
type Inner = T;
|
||||
|
||||
default fn return_type_into_py_result(self) -> PyResult<Self::Inner> {
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IntoPyObject> ReturnTypeIntoPyResult for PyResult<T> {
|
||||
type Inner = T;
|
||||
|
||||
fn return_type_into_py_result(self) -> PyResult<Self::Inner> {
|
||||
self
|
||||
}
|
||||
}
|
|
@ -140,7 +140,7 @@ pub struct PyDowncastError;
|
|||
/// Helper conversion trait that allows to use custom arguments for exception constructor.
|
||||
pub trait PyErrArguments {
|
||||
/// Arguments for exception
|
||||
fn arguments(&self, Python) -> PyObject;
|
||||
fn arguments(&self, _: Python) -> PyObject;
|
||||
}
|
||||
|
||||
impl PyErr {
|
||||
|
|
18
src/lib.rs
18
src/lib.rs
|
@ -167,13 +167,14 @@ pub use python::{Python, ToPyPointer, IntoPyPointer, IntoPyDictPointer};
|
|||
pub use pythonrun::{GILGuard, GILPool, prepare_freethreaded_python, prepare_pyo3_library};
|
||||
pub use instance::{PyToken, PyObjectWithToken, AsPyRef, Py, PyNativeType};
|
||||
pub use conversion::{FromPyObject, PyTryFrom, PyTryInto,
|
||||
ToPyObject, ToBorrowedObject, IntoPyObject, IntoPyTuple};
|
||||
ToPyObject, ToBorrowedObject, IntoPyObject, IntoPyTuple,
|
||||
ReturnTypeIntoPyResult};
|
||||
pub mod class;
|
||||
pub use class::*;
|
||||
|
||||
/// Procedural macros
|
||||
pub mod py {
|
||||
pub use pyo3cls::{proto, class, methods};
|
||||
pub use pyo3cls::{proto, class, methods, function};
|
||||
|
||||
#[cfg(Py_3)]
|
||||
pub use pyo3cls::mod3init as modinit;
|
||||
|
@ -183,13 +184,24 @@ pub mod py {
|
|||
}
|
||||
|
||||
/// Constructs a `&'static CStr` literal.
|
||||
macro_rules! cstr(
|
||||
macro_rules! cstr {
|
||||
($s: tt) => (
|
||||
// TODO: verify that $s is a string literal without nuls
|
||||
unsafe {
|
||||
::std::ffi::CStr::from_ptr(concat!($s, "\0").as_ptr() as *const _)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns a function that takes a Python instance and returns a python function.
|
||||
///
|
||||
/// Use this together with `#[function]` and [PyModule::add_function].
|
||||
#[macro_export]
|
||||
macro_rules! wrap_function (
|
||||
($function_name:ident) => {
|
||||
// Make sure this ident matches the one in function_wrapper_ident
|
||||
&concat_idents!(__pyo3_get_function_, $function_name)
|
||||
};
|
||||
);
|
||||
|
||||
mod python;
|
||||
|
|
|
@ -16,7 +16,6 @@ use objectprotocol::ObjectProtocol;
|
|||
use instance::PyObjectWithToken;
|
||||
use err::{PyResult, PyErr};
|
||||
|
||||
|
||||
/// Represents a Python `module` object.
|
||||
pub struct PyModule(PyObject);
|
||||
|
||||
|
@ -140,4 +139,23 @@ impl PyModule {
|
|||
|
||||
self.setattr(T::NAME, ty)
|
||||
}
|
||||
|
||||
/// Adds a function to a module, using the functions __name__ as name.
|
||||
///
|
||||
/// Use this together with the`#[function]` and [wrap_function!] macro.
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// m.add_function(wrap_function!(double));
|
||||
/// ```
|
||||
///
|
||||
/// You can also add a function with a custom name using [add](PyModule::add):
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// m.add("also_double", wrap_function!(double)(py));
|
||||
/// ```
|
||||
pub fn add_function(&self, wrapper: &Fn(Python) -> PyObject) -> PyResult<()> {
|
||||
let function = wrapper(self.py());
|
||||
let name = function.getattr(self.py(), "__name__").expect("A function must have a __name__");
|
||||
self.add(name.extract(self.py()).unwrap(), function)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -399,7 +399,7 @@ impl<'p> Python<'p> {
|
|||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
/// Pass value owneship to `Python` object and get reference back.
|
||||
/// Pass value ownership to `Python` object and get reference back.
|
||||
/// Value get cleaned up on the GIL release.
|
||||
pub fn register_any<T: 'static>(self, ob: T) -> &'p T {
|
||||
unsafe { pythonrun::register_any(ob) }
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
#![feature(proc_macro, specialization)]
|
||||
#![feature(proc_macro, specialization, concat_idents)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate pyo3;
|
||||
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::py::{class, modinit};
|
||||
use pyo3::{PyDict, PyModule, PyObject, PyResult, Python};
|
||||
use pyo3::py::{class, function, modinit};
|
||||
|
||||
|
||||
#[class]
|
||||
|
@ -13,6 +14,11 @@ fn sum_as_string(a: i64, b: i64) -> String {
|
|||
format!("{}", a + b).to_string()
|
||||
}
|
||||
|
||||
#[function]
|
||||
fn double(x: usize) -> usize {
|
||||
x * 2
|
||||
}
|
||||
|
||||
/// This module is implemented in Rust.
|
||||
#[modinit(module_with_functions)]
|
||||
fn init_mod(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
|
@ -31,6 +37,9 @@ fn init_mod(py: Python, m: &PyModule) -> PyResult<()> {
|
|||
|
||||
m.add("foo", "bar").unwrap();
|
||||
|
||||
m.add_function(wrap_function!(double)).unwrap();
|
||||
m.add("also_double", wrap_function!(double)(py)).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -47,4 +56,6 @@ fn test_module_with_functions() {
|
|||
py.run("assert module_with_functions.no_parameters() == 42", None, Some(d)).unwrap();
|
||||
py.run("assert module_with_functions.foo == 'bar'", None, Some(d)).unwrap();
|
||||
py.run("assert module_with_functions.EmptyClass != None", None, Some(d)).unwrap();
|
||||
py.run("assert module_with_functions.double(3) == 6", None, Some(d)).unwrap();
|
||||
py.run("assert module_with_functions.also_double(3) == 6", None, Some(d)).unwrap();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue