Add wrap_module macro

This commit is contained in:
konstin 2018-11-12 22:25:45 +01:00
parent 3de622cdfd
commit 863ffb161f
12 changed files with 145 additions and 45 deletions

View File

@ -6,6 +6,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
## [Unreleased] ## [Unreleased]
### Added
* Added a `wrap_module!` macro similar to the existing `wrap_function!` macro. Only available on python 3
### Changed
* Renamed `add_function` to `add_wrapped` as it now also supports modules.
### Removed ### Removed
* `PyToken` was removed due to unsoundness (See [#94](https://github.com/PyO3/pyo3/issues/94)). * `PyToken` was removed due to unsoundness (See [#94](https://github.com/PyO3/pyo3/issues/94)).

View File

@ -58,7 +58,7 @@ fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
/// This module is a python module implemented in Rust. /// This module is a python module implemented in Rust.
#[pymodinit] #[pymodinit]
fn string_sum(py: Python, m: &PyModule) -> PyResult<()> { fn string_sum(py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_function!(sum_as_string))?; m.add_wrapped(wrap_function!(sum_as_string))?;
Ok(()) Ok(())
} }

View File

@ -204,26 +204,26 @@ impl TzClass {
#[pymodinit] #[pymodinit]
fn datetime(_py: Python, m: &PyModule) -> PyResult<()> { fn datetime(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_function!(make_date))?; m.add_wrapped(wrap_function!(make_date))?;
m.add_function(wrap_function!(get_date_tuple))?; m.add_wrapped(wrap_function!(get_date_tuple))?;
m.add_function(wrap_function!(date_from_timestamp))?; m.add_wrapped(wrap_function!(date_from_timestamp))?;
m.add_function(wrap_function!(make_time))?; m.add_wrapped(wrap_function!(make_time))?;
m.add_function(wrap_function!(get_time_tuple))?; m.add_wrapped(wrap_function!(get_time_tuple))?;
m.add_function(wrap_function!(make_delta))?; m.add_wrapped(wrap_function!(make_delta))?;
m.add_function(wrap_function!(get_delta_tuple))?; m.add_wrapped(wrap_function!(get_delta_tuple))?;
m.add_function(wrap_function!(make_datetime))?; m.add_wrapped(wrap_function!(make_datetime))?;
m.add_function(wrap_function!(get_datetime_tuple))?; m.add_wrapped(wrap_function!(get_datetime_tuple))?;
m.add_function(wrap_function!(datetime_from_timestamp))?; m.add_wrapped(wrap_function!(datetime_from_timestamp))?;
// Python 3.6+ functions // Python 3.6+ functions
#[cfg(Py_3_6)] #[cfg(Py_3_6)]
{ {
m.add_function(wrap_function!(time_with_fold))?; m.add_wrapped(wrap_function!(time_with_fold))?;
m.add_function(wrap_function!(get_time_tuple_fold))?; m.add_wrapped(wrap_function!(get_time_tuple_fold))?;
m.add_function(wrap_function!(get_datetime_tuple_fold))?; m.add_wrapped(wrap_function!(get_datetime_tuple_fold))?;
} }
m.add_function(wrap_function!(issue_219))?; m.add_wrapped(wrap_function!(issue_219))?;
m.add_class::<TzClass>()?; m.add_class::<TzClass>()?;
Ok(()) Ok(())

View File

@ -30,7 +30,7 @@ fn double(x: i32) -> i32 {
#[pymodinit] #[pymodinit]
fn othermod(_py: Python, m: &PyModule) -> PyResult<()> { fn othermod(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_function!(double))?; m.add_wrapped(wrap_function!(double))?;
m.add_class::<ModClass>()?; m.add_class::<ModClass>()?;
m.add("USIZE_MIN", usize::min_value())?; m.add("USIZE_MIN", usize::min_value())?;

View File

@ -80,7 +80,7 @@ fn count_line(line: &str, needle: &str) -> usize {
#[pymodinit] #[pymodinit]
fn word_count(_py: Python, m: &PyModule) -> PyResult<()> { fn word_count(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_function!(count_line))?; m.add_wrapped(wrap_function!(count_line))?;
m.add_class::<WordCounter>()?; m.add_class::<WordCounter>()?;
Ok(()) Ok(())

View File

@ -29,7 +29,7 @@ fn rust2py(py: Python, m: &PyModule) -> PyResult<()> {
``` ```
The other is annotating a function with `#[py::function]` and then adding it 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 to the module using the `add_wrapped_to_module!` macro, which takes the module
as first parameter, the function name as second and an instance of `Python` as first parameter, the function name as second and an instance of `Python`
as third. as third.
@ -47,7 +47,7 @@ fn double(x: usize) -> usize {
#[pymodinit] #[pymodinit]
fn module_with_functions(py: Python, m: &PyModule) -> PyResult<()> { fn module_with_functions(py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_function!(double)).unwrap(); m.add_wrapped(wrap_function!(double)).unwrap();
Ok(()) Ok(())
} }

View File

@ -8,8 +8,6 @@ As shown in the Getting Started chapter, you can create a module as follows:
extern crate pyo3; extern crate pyo3;
use pyo3::{PyResult, Python, PyModule}; use pyo3::{PyResult, Python, PyModule};
// add bindings to the generated python module // add bindings to the generated python module
// N.B: names: "librust2py" must be the name of the `.so` or `.pyd` file // N.B: names: "librust2py" must be the name of the `.so` or `.pyd` file
/// This module is implemented in Rust. /// This module is implemented in Rust.
@ -36,7 +34,11 @@ fn sum_as_string(a:i64, b:i64) -> String {
# fn main() {} # fn main() {}
``` ```
The `#[pymodinit}` procedural macro attribute takes care of exporting the initialization function of your module to Python. It takes one argument as the name of your module, it must be the name of the `.so` or `.pyd` file. The `#[pymodinit]` procedural macro attribute takes care of exporting the initialization function of your module to Python. It takes one argument as the name of your module, which must be the name of the `.so` or `.pyd` file.
To import the module, either copy the shared library as described in [Get Started](./overview.md) or use a tool, e.g. `pyo3-pack develop` with [pyo3-pack](https://github.com/PyO3/pyo3-pack) or `python setup.py develop` with [setuptools-rust](https://github.com/PyO3/setuptools-rust).
## Documentation
The [Rust doc comments](https://doc.rust-lang.org/stable/book/first-edition/comments.html) of the module initialization function will be applied automatically as the Python doc string of your module. The [Rust doc comments](https://doc.rust-lang.org/stable/book/first-edition/comments.html) of the module initialization function will be applied automatically as the Python doc string of your module.
@ -48,9 +50,34 @@ print(rust2py.__doc__)
Which means that the above Python code will print `This module is implemented in Rust.`. Which means that the above Python code will print `This module is implemented in Rust.`.
> On macOS, you will need to rename the output from `*.dylib` to `*.so`. ## Modules as objects
>
> On Windows, you will need to rename the output from `*.dll` to `*.pyd`.
For `setup.py` integration, You can use [setuptools-rust](https://github.com/PyO3/setuptools-rust), In python, modules are first class objects. This means can store them as values or add them to dicts or other modules:
learn more about it in [Distribution](./distribution.html).
```rust
#[pyfunction]
fn subfunction() -> String {
"Subfunction".to_string()
}
#[pymodule]
fn submodule(_py: Python, module: &PyModule) -> PyResult<()> {
module.add_wrapped(wrap_function!(subfunction))?;
Ok(())
}
#[pymodule]
fn supermodule(_py: Python, module: &PyModule) -> PyResult<()> {
module.add_wrapped(wrap_module!(submodule))?;
Ok(())
}
fn nested_call() {
let gil = GILGuard::acquire();
let py = gil.python();
let supermodule = wrap_module!(supermodule)(py);
ctx.set_item("supermodule", supermodule);
py.run("assert supermodule.submodule.subfuntion() == 'Subfunction'", None, Some(&ctx)).unwrap();
}
```

View File

@ -49,7 +49,7 @@ fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
/// This module is a python moudle implemented in Rust. /// This module is a python moudle implemented in Rust.
#[pymodinit] #[pymodinit]
fn rust_py(py: Python, m: &PyModule) -> PyResult<()> { fn rust_py(py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_function!(sum_as_string))?; m.add_wrapped(wrap_function!(sum_as_string))?;
Ok(()) Ok(())
} }

View File

@ -12,7 +12,7 @@ use proc_macro2::{Span, TokenStream};
/// Generates the function that is called by the python interpreter to initialize the native /// Generates the function that is called by the python interpreter to initialize the native
/// module /// module
pub fn py3_init(fnname: &syn::Ident, name: &syn::Ident, doc: syn::Lit) -> TokenStream { pub fn py3_init(fnname: &syn::Ident, name: &syn::Ident, doc: syn::Lit) -> TokenStream {
let cb_name: syn::Ident = syn::parse_str(&format!("PyInit_{}", name)).unwrap(); let cb_name = syn::Ident::new(&format!("PyInit_{}", name), Span::call_site());
quote! { quote! {
#[no_mangle] #[no_mangle]
@ -26,7 +26,7 @@ pub fn py3_init(fnname: &syn::Ident, name: &syn::Ident, doc: syn::Lit) -> TokenS
} }
pub fn py2_init(fnname: &syn::Ident, name: &syn::Ident, doc: syn::Lit) -> TokenStream { pub fn py2_init(fnname: &syn::Ident, name: &syn::Ident, doc: syn::Lit) -> TokenStream {
let cb_name: syn::Ident = syn::parse_str(&format!("init{}", name)).unwrap(); let cb_name = syn::Ident::new(&format!("init{}", name), Span::call_site());
quote! { quote! {
#[no_mangle] #[no_mangle]
@ -51,7 +51,7 @@ pub fn process_functions_in_module(func: &mut syn::ItemFn) {
let item: syn::ItemFn = parse_quote!{ let item: syn::ItemFn = parse_quote!{
fn block_wrapper() { fn block_wrapper() {
#function_to_python #function_to_python
#module_name.add_function(&#function_wrapper_ident)?; #module_name.add_wrapped(&#function_wrapper_ident)?;
} }
}; };
stmts.extend(item.block.stmts.into_iter()); stmts.extend(item.block.stmts.into_iter());

View File

@ -201,7 +201,7 @@ pub mod proc_macro {
/// Returns a function that takes a [Python] instance and returns a python function. /// Returns a function that takes a [Python] instance and returns a python function.
/// ///
/// Use this together with `#[function]` and [types::PyModule::add_function]. /// Use this together with `#[pyfunction]` and [types::PyModule::add_wrapped].
#[macro_export] #[macro_export]
macro_rules! wrap_function { macro_rules! wrap_function {
($function_name:ident) => {{ ($function_name:ident) => {{
@ -218,3 +218,22 @@ macro_rules! wrap_function {
} }
}}; }};
} }
/// Returns a function that takes a [Python] instance and returns a python module.
///
/// Use this together with `#[pymodule]` and [types::PyModule::add_wrapped].
#[cfg(Py_3)]
#[macro_export]
macro_rules! wrap_module {
($module_name:ident) => {{
use $crate::mashup::*;
mashup! {
m["method"] = PyInit_ $module_name;
}
m! {
&|py| unsafe { crate::PyObject::from_owned_ptr(py, "method"()) }
}
}};
}

View File

@ -169,12 +169,14 @@ impl PyModule {
self.setattr(T::NAME, ty) self.setattr(T::NAME, ty)
} }
/// Adds a function to a module, using the functions __name__ as name. /// Adds a function or a (sub)module to a module, using the functions __name__ as name.
/// ///
/// Use this together with the`#[pyfunction]` and [wrap_function!] macro. /// Use this together with the`#[pyfunction]` and [wrap_function!] or `#[pymodule]` and
/// [wrap_module!].
/// ///
/// ```rust,ignore /// ```rust,ignore
/// m.add_function(wrap_function!(double)); /// m.add_wrapped(wrap_function!(double));
/// m.add_wrapped(wrap_module!(utils));
/// ``` /// ```
/// ///
/// You can also add a function with a custom name using [add](PyModule::add): /// You can also add a function with a custom name using [add](PyModule::add):
@ -182,11 +184,11 @@ impl PyModule {
/// ```rust,ignore /// ```rust,ignore
/// m.add("also_double", wrap_function!(double)(py)); /// m.add("also_double", wrap_function!(double)(py));
/// ``` /// ```
pub fn add_function(&self, wrapper: &Fn(Python) -> PyObject) -> PyResult<()> { pub fn add_wrapped(&self, wrapper: &Fn(Python) -> PyObject) -> PyResult<()> {
let function = wrapper(self.py()); let function = wrapper(self.py());
let name = function let name = function
.getattr(self.py(), "__name__") .getattr(self.py(), "__name__")
.expect("A function must have a __name__"); .expect("A function or module must have a __name__");
self.add(name.extract(self.py()).unwrap(), function) self.add(name.extract(self.py()).unwrap(), function)
} }
} }

View File

@ -40,22 +40,22 @@ fn module_with_functions(py: Python, m: &PyModule) -> PyResult<()> {
m.add("foo", "bar").unwrap(); m.add("foo", "bar").unwrap();
m.add_function(wrap_function!(double)).unwrap(); m.add_wrapped(wrap_function!(double)).unwrap();
m.add("also_double", wrap_function!(double)(py)).unwrap(); m.add("also_double", wrap_function!(double)(py)).unwrap();
Ok(()) Ok(())
} }
#[test] #[test]
#[cfg(Py_3)]
fn test_module_with_functions() { fn test_module_with_functions() {
let gil = Python::acquire_gil(); let gil = Python::acquire_gil();
let py = gil.python(); let py = gil.python();
let d = PyDict::new(py); let d = PyDict::new(py);
d.set_item("module_with_functions", unsafe { d.set_item(
PyObject::from_owned_ptr(py, PyInit_module_with_functions()) "module_with_functions",
}) wrap_module!(module_with_functions)(py),
)
.unwrap(); .unwrap();
let run = |code| py.run(code, None, Some(d)).unwrap(); let run = |code| py.run(code, None, Some(d)).unwrap();
@ -131,16 +131,61 @@ fn r#move() -> usize {
#[pymodinit] #[pymodinit]
fn raw_ident_module(_py: Python, module: &PyModule) -> PyResult<()> { fn raw_ident_module(_py: Python, module: &PyModule) -> PyResult<()> {
module.add_function(wrap_function!(r#move)) module.add_wrapped(wrap_function!(r#move))
} }
#[test] #[test]
#[cfg(Py_3)]
fn test_raw_idents() { fn test_raw_idents() {
let gil = Python::acquire_gil(); let gil = Python::acquire_gil();
let py = gil.python(); let py = gil.python();
let module = unsafe { PyObject::from_owned_ptr(py, PyInit_raw_ident_module()) }; let module = wrap_module!(raw_ident_module)(py);
py_assert!(py, module, "module.move() == 42"); py_assert!(py, module, "module.move() == 42");
} }
#[pyfunction]
#[cfg(Py_3)]
fn subfunction() -> String {
"Subfunction".to_string()
}
#[pymodinit]
#[cfg(Py_3)]
fn submodule(_py: Python, module: &PyModule) -> PyResult<()> {
module.add_wrapped(wrap_function!(subfunction))?;
Ok(())
}
#[pyfunction]
#[cfg(Py_3)]
fn superfunction() -> String {
"Superfunction".to_string()
}
#[pymodinit]
#[cfg(Py_3)]
fn supermodule(_py: Python, module: &PyModule) -> PyResult<()> {
module.add_wrapped(wrap_function!(superfunction))?;
module.add_wrapped(wrap_module!(submodule))?;
Ok(())
}
#[test]
#[cfg(Py_3)]
fn test_module_nesting() {
let gil = GILGuard::acquire();
let py = gil.python();
let supermodule = wrap_module!(supermodule)(py);
py_assert!(
py,
supermodule,
"supermodule.superfunction() == 'Superfunction'"
);
py_assert!(
py,
supermodule,
"supermodule.submodule.subfunction() == 'Subfunction'"
);
}