Add wrap_module macro
This commit is contained in:
parent
3de622cdfd
commit
863ffb161f
|
@ -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)).
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(())
|
||||||
|
|
|
@ -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())?;
|
||||||
|
|
|
@ -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(())
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
|
|
21
src/lib.rs
21
src/lib.rs
|
@ -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"()) }
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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'"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue