Merge pull request #2081 from davidhewitt/wrap-paths

macros: accept paths in wrap_x macros
This commit is contained in:
David Hewitt 2021-12-30 14:00:48 +00:00 committed by GitHub
commit 24eea5db9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 309 additions and 140 deletions

View File

@ -12,10 +12,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Update MSRV to Rust 1.48. [#2004](https://github.com/PyO3/pyo3/pull/2004)
- Update `indoc` optional dependency to 1.0. [#2004](https://github.com/PyO3/pyo3/pull/2004)
- Update `paste` optional dependency to 1.0. [#2004](https://github.com/PyO3/pyo3/pull/2004)
- Drop support for Python 3.6, remove `abi3-py36` feature. [#2006](https://github.com/PyO3/pyo3/pull/2006)
- `pyo3-build-config` no longer enables the `resolve-config` feature by default. [#2008](https://github.com/PyO3/pyo3/pull/2008)
- Update `inventory` optional dependency to 0.2. [#2019](https://github.com/PyO3/pyo3/pull/2019)
- Drop `paste` dependency. [#2081](https://github.com/PyO3/pyo3/pull/2081)
### Added
@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- All PyO3 proc-macros except the deprecated `#[pyproto]` now accept a supplemental attribute `#[pyo3(crate = "some::path")]` that specifies
where to find the `pyo3` crate, in case it has been renamed or is re-exported and not found at the crate root. [#2022](https://github.com/PyO3/pyo3/pull/2022)
- Expose `pyo3-build-config` APIs for cross-compiling and Python configuration discovery for use in other projects. [#1996](https://github.com/PyO3/pyo3/pull/1996)
- Accept paths in `wrap_pyfunction` and `wrap_pymodule`. [#2081](https://github.com/PyO3/pyo3/pull/2081)
### Changed
@ -55,6 +56,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix memory leak in `PyErr::into_value`. [#2026](https://github.com/PyO3/pyo3/pull/2026)
- Fix clippy warning `needless-option-as-deref` in code generated by `#[pyfunction]` and `#[pymethods]`. [#2040](https://github.com/PyO3/pyo3/pull/2040)
- Fix undefined behavior in `PySlice::indices`. [#2061](https://github.com/PyO3/pyo3/pull/2061)
- Use the Rust function path for `wrap_pymodule!` of a `#[pymodule]` with a `#[pyo3(name = "..")]` attribute, not the Python name. [#2081](https://github.com/PyO3/pyo3/pull/2081)
## [0.15.1] - 2021-11-19

View File

@ -22,7 +22,6 @@ parking_lot = "0.11.0"
# support crates for macros feature
pyo3-macros = { path = "pyo3-macros", version = "=0.15.1", optional = true }
indoc = { version = "1.0.3", optional = true }
paste = { version = "1.0.6", optional = true }
unindent = { version = "0.1.4", optional = true }
# support crate for multiple-pymethods feature
@ -53,7 +52,7 @@ pyo3-build-config = { path = "pyo3-build-config", version = "0.15.1", features =
default = ["macros"]
# Enables macros: #[pyclass], #[pymodule], #[pyfunction] etc.
macros = ["pyo3-macros", "indoc", "paste", "unindent"]
macros = ["pyo3-macros", "indoc", "unindent"]
# Enables multiple #[pymethods] per #[pyclass]
multiple-pymethods = ["inventory", "pyo3-macros/multiple-pymethods"]

View File

@ -22,11 +22,13 @@ mod pyfunction;
mod pyimpl;
mod pymethod;
mod pyproto;
mod wrap;
pub use frompyobject::build_derive_from_pyobject;
pub use module::{process_functions_in_module, py_init, PyModuleOptions};
pub use module::{process_functions_in_module, pymodule_impl, PyModuleOptions};
pub use pyclass::{build_py_class, build_py_enum, PyClassArgs};
pub use pyfunction::{build_py_function, PyFunctionOptions};
pub use pyimpl::{build_py_methods, PyClassMethodsType};
pub use pyproto::build_py_proto;
pub use utils::get_doc;
pub use wrap::{wrap_pyfunction_impl, wrap_pymodule_impl, WrapPyFunctionArgs};

View File

@ -7,6 +7,7 @@ use crate::{
},
pyfunction::{impl_wrap_pyfunction, PyFunctionOptions},
utils::{get_pyo3_crate, PythonDoc},
wrap::module_def_ident,
};
use proc_macro2::{Span, TokenStream};
use quote::quote;
@ -15,7 +16,7 @@ use syn::{
parse::{Parse, ParseStream},
spanned::Spanned,
token::Comma,
Ident, Path, Result,
Ident, Path, Result, Visibility,
};
#[derive(Default)]
@ -61,25 +62,32 @@ impl PyModuleOptions {
/// Generates the function that is called by the python interpreter to initialize the native
/// module
pub fn py_init(fnname: &Ident, options: PyModuleOptions, doc: PythonDoc) -> TokenStream {
pub fn pymodule_impl(
fnname: &Ident,
options: PyModuleOptions,
doc: PythonDoc,
visibility: &Visibility,
) -> TokenStream {
let name = options.name.unwrap_or_else(|| fnname.unraw());
let krate = get_pyo3_crate(&options.krate);
let cb_name = Ident::new(&format!("PyInit_{}", name), Span::call_site());
let module_def_name = module_def_ident(fnname);
quote! {
#[no_mangle]
#[allow(non_snake_case)]
/// This autogenerated function is called by the python interpreter when importing
/// the module.
pub unsafe extern "C" fn #cb_name() -> *mut #krate::ffi::PyObject {
use #krate as _pyo3;
use _pyo3::derive_utils::ModuleDef;
static NAME: &str = concat!(stringify!(#name), "\0");
static DOC: &str = #doc;
static MODULE_DEF: ModuleDef = unsafe { ModuleDef::new(NAME, DOC) };
_pyo3::callback::handle_panic(|_py| { MODULE_DEF.make_module(_py, #fnname) })
use #krate::{self as _pyo3, IntoPyPointer};
_pyo3::callback::handle_panic(|_py| ::std::result::Result::Ok(#module_def_name.make_module(_py)?.into_ptr()))
}
#[doc(hidden)]
#visibility static #module_def_name: #krate::impl_::pymodule::ModuleDef = unsafe {
#krate::impl_::pymodule::ModuleDef::new(concat!(stringify!(#name), "\0"), #doc, #krate::impl_::pymodule::ModuleInitializer(#fnname))
};
}
}

View File

@ -9,6 +9,7 @@ use crate::{
method::{self, CallingConvention, FnArg},
pymethod::check_generic,
utils::{self, ensure_not_async_fn, get_pyo3_crate},
wrap::function_wrapper_ident,
};
use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote};
@ -366,12 +367,6 @@ pub fn build_py_function(
Ok(impl_wrap_pyfunction(ast, options)?.1)
}
/// Coordinates the naming of a the add-function-to-python-module function
fn function_wrapper_ident(name: &Ident) -> Ident {
// Make sure this ident matches the one of wrap_pyfunction
format_ident!("__pyo3_get_function_{}", name)
}
/// Generates python wrapper over a function that allows adding it to a python module as a python
/// function
pub fn impl_wrap_pyfunction(

View File

@ -0,0 +1,67 @@
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{parse::Parse, spanned::Spanned, Ident, Token};
pub struct WrapPyFunctionArgs {
function: syn::Path,
comma_and_arg: Option<(Token![,], syn::Expr)>,
}
impl Parse for WrapPyFunctionArgs {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let function = input.parse()?;
let comma_and_arg = if !input.is_empty() {
Some((input.parse()?, input.parse()?))
} else {
None
};
Ok(Self {
function,
comma_and_arg,
})
}
}
pub fn wrap_pyfunction_impl(args: WrapPyFunctionArgs) -> syn::Result<TokenStream> {
let WrapPyFunctionArgs {
mut function,
comma_and_arg,
} = args;
let span = function.span();
let last_segment = function
.segments
.last_mut()
.ok_or_else(|| err_spanned!(span => "expected non-empty path"))?;
last_segment.ident = function_wrapper_ident(&last_segment.ident);
let output = if let Some((_, arg)) = comma_and_arg {
quote! { #function(#arg) }
} else {
quote! { &|arg| #function(arg) }
};
Ok(output)
}
pub fn wrap_pymodule_impl(mut module_path: syn::Path) -> syn::Result<TokenStream> {
let span = module_path.span();
let last_segment = module_path
.segments
.last_mut()
.ok_or_else(|| err_spanned!(span => "expected non-empty path"))?;
last_segment.ident = module_def_ident(&last_segment.ident);
Ok(quote! {
&|py| unsafe { #module_path.make_module(py).expect("failed to wrap pymodule") }
})
}
pub(crate) fn function_wrapper_ident(name: &Ident) -> Ident {
format_ident!("__pyo3_get_function_{}", name)
}
pub(crate) fn module_def_ident(name: &Ident) -> Ident {
format_ident!("__PYO3_PYMODULE_DEF_{}", name.to_string().to_uppercase())
}

View File

@ -17,6 +17,7 @@ proc-macro = true
multiple-pymethods = []
[dependencies]
proc-macro2 = { version = "1", default-features = false }
quote = "1"
syn = { version = "1", features = ["full", "extra-traits"] }
pyo3-macros-backend = { path = "../pyo3-macros-backend", version = "=0.15.1" }

View File

@ -6,10 +6,12 @@
extern crate proc_macro;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use pyo3_macros_backend::{
build_derive_from_pyobject, build_py_class, build_py_enum, build_py_function, build_py_methods,
build_py_proto, get_doc, process_functions_in_module, py_init, PyClassArgs, PyClassMethodsType,
PyFunctionOptions, PyModuleOptions,
build_py_proto, get_doc, process_functions_in_module, pymodule_impl, wrap_pyfunction_impl,
wrap_pymodule_impl, PyClassArgs, PyClassMethodsType, PyFunctionOptions, PyModuleOptions,
WrapPyFunctionArgs,
};
use quote::quote;
use syn::{parse::Nothing, parse_macro_input};
@ -46,7 +48,7 @@ pub fn pymodule(args: TokenStream, input: TokenStream) -> TokenStream {
let doc = get_doc(&ast.attrs, None);
let expanded = py_init(&ast.sig.ident, options, doc);
let expanded = pymodule_impl(&ast.sig.ident, options, doc, &ast.vis);
quote!(
#ast
@ -68,7 +70,7 @@ pub fn pymodule(args: TokenStream, input: TokenStream) -> TokenStream {
#[proc_macro_attribute]
pub fn pyproto(_: TokenStream, input: TokenStream) -> TokenStream {
let mut ast = parse_macro_input!(input as syn::ItemImpl);
let expanded = build_py_proto(&mut ast).unwrap_or_else(|e| e.to_compile_error());
let expanded = build_py_proto(&mut ast).unwrap_or_compile_error();
quote!(
#ast
@ -180,7 +182,7 @@ pub fn pyfunction(attr: TokenStream, input: TokenStream) -> TokenStream {
let mut ast = parse_macro_input!(input as syn::ItemFn);
let options = parse_macro_input!(attr as PyFunctionOptions);
let expanded = build_py_function(&mut ast, options).unwrap_or_else(|e| e.to_compile_error());
let expanded = build_py_function(&mut ast, options).unwrap_or_compile_error();
quote!(
#ast
@ -192,21 +194,39 @@ pub fn pyfunction(attr: TokenStream, input: TokenStream) -> TokenStream {
#[proc_macro_derive(FromPyObject, attributes(pyo3))]
pub fn derive_from_py_object(item: TokenStream) -> TokenStream {
let ast = parse_macro_input!(item as syn::DeriveInput);
let expanded = build_derive_from_pyobject(&ast).unwrap_or_else(|e| e.to_compile_error());
let expanded = build_derive_from_pyobject(&ast).unwrap_or_compile_error();
quote!(
#expanded
)
.into()
}
/// Wraps a Rust function annotated with [`#[pyfunction]`](macro@crate::pyfunction).
///
/// This can be used with `PyModule::add_function` to add free functions to a `PyModule` - see its
/// documentation for more information.
#[proc_macro]
pub fn wrap_pyfunction(input: TokenStream) -> TokenStream {
let args = parse_macro_input!(input as WrapPyFunctionArgs);
wrap_pyfunction_impl(args).unwrap_or_compile_error().into()
}
/// Returns a function that takes a `Python` instance and returns a Python module.
///
/// Use this together with [`#[pymodule]`](macro@crate::pymodule) and `PyModule::add_wrapped`.
#[proc_macro]
pub fn wrap_pymodule(input: TokenStream) -> TokenStream {
let path = parse_macro_input!(input as syn::Path);
wrap_pymodule_impl(path).unwrap_or_compile_error().into()
}
fn pyclass_impl(
attrs: TokenStream,
mut ast: syn::ItemStruct,
methods_type: PyClassMethodsType,
) -> TokenStream {
let args = parse_macro_input!(attrs with PyClassArgs::parse_stuct_args);
let expanded =
build_py_class(&mut ast, &args, methods_type).unwrap_or_else(|e| e.to_compile_error());
let expanded = build_py_class(&mut ast, &args, methods_type).unwrap_or_compile_error();
quote!(
#ast
@ -221,8 +241,7 @@ fn pyclass_enum_impl(
methods_type: PyClassMethodsType,
) -> TokenStream {
let args = parse_macro_input!(attrs with PyClassArgs::parse_enum_args);
let expanded =
build_py_enum(&mut ast, &args, methods_type).unwrap_or_else(|e| e.into_compile_error());
let expanded = build_py_enum(&mut ast, &args, methods_type).unwrap_or_compile_error();
quote!(
#ast
@ -233,8 +252,7 @@ fn pyclass_enum_impl(
fn pymethods_impl(input: TokenStream, methods_type: PyClassMethodsType) -> TokenStream {
let mut ast = parse_macro_input!(input as syn::ItemImpl);
let expanded =
build_py_methods(&mut ast, methods_type).unwrap_or_else(|e| e.to_compile_error());
let expanded = build_py_methods(&mut ast, methods_type).unwrap_or_compile_error();
quote!(
#ast
@ -250,3 +268,13 @@ fn methods_type() -> PyClassMethodsType {
PyClassMethodsType::Specialization
}
}
trait UnwrapOrCompileError {
fn unwrap_or_compile_error(self) -> TokenStream2;
}
impl UnwrapOrCompileError for syn::Result<TokenStream2> {
fn unwrap_or_compile_error(self) -> TokenStream2 {
self.unwrap_or_else(|e| e.into_compile_error())
}
}

View File

@ -203,7 +203,7 @@ impl TzClass {
}
#[pymodule]
fn datetime(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
pub fn datetime(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(make_date, m)?)?;
m.add_function(wrap_pyfunction!(get_date_tuple, m)?)?;
m.add_function(wrap_pyfunction!(date_from_timestamp, m)?)?;

View File

@ -3,7 +3,7 @@ use pyo3::prelude::*;
use pyo3::types::PyDict;
#[pymodule]
fn dict_iter(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
pub fn dict_iter(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_class::<DictSize>()?;
Ok(())
}

View File

@ -12,31 +12,19 @@ pub mod path;
pub mod pyclass_iter;
pub mod subclassing;
#[cfg(not(Py_LIMITED_API))]
use buf_and_str::*;
#[cfg(not(Py_LIMITED_API))]
use datetime::*;
use dict_iter::*;
use misc::*;
use objstore::*;
use othermod::*;
use path::*;
use pyclass_iter::*;
use subclassing::*;
#[pymodule]
fn pyo3_pytests(py: Python, m: &PyModule) -> PyResult<()> {
#[cfg(not(Py_LIMITED_API))]
m.add_wrapped(wrap_pymodule!(buf_and_str))?;
m.add_wrapped(wrap_pymodule!(buf_and_str::buf_and_str))?;
#[cfg(not(Py_LIMITED_API))]
m.add_wrapped(wrap_pymodule!(datetime))?;
m.add_wrapped(wrap_pymodule!(dict_iter))?;
m.add_wrapped(wrap_pymodule!(misc))?;
m.add_wrapped(wrap_pymodule!(objstore))?;
m.add_wrapped(wrap_pymodule!(othermod))?;
m.add_wrapped(wrap_pymodule!(path))?;
m.add_wrapped(wrap_pymodule!(pyclass_iter))?;
m.add_wrapped(wrap_pymodule!(subclassing))?;
m.add_wrapped(wrap_pymodule!(datetime::datetime))?;
m.add_wrapped(wrap_pymodule!(dict_iter::dict_iter))?;
m.add_wrapped(wrap_pymodule!(misc::misc))?;
m.add_wrapped(wrap_pymodule!(objstore::objstore))?;
m.add_wrapped(wrap_pymodule!(othermod::othermod))?;
m.add_wrapped(wrap_pymodule!(path::path))?;
m.add_wrapped(wrap_pymodule!(pyclass_iter::pyclass_iter))?;
m.add_wrapped(wrap_pymodule!(subclassing::subclassing))?;
// Inserting to sys.modules allows importing submodules nicely from Python
// e.g. import pyo3_pytests.buf_and_str as bas

View File

@ -8,7 +8,7 @@ fn issue_219() {
}
#[pymodule]
fn misc(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
pub fn misc(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(issue_219, m)?)?;
Ok(())
}

View File

@ -19,6 +19,6 @@ impl ObjStore {
}
#[pymodule]
fn objstore(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
pub fn objstore(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_class::<ObjStore>()
}

View File

@ -29,7 +29,7 @@ fn double(x: i32) -> i32 {
}
#[pymodule]
fn othermod(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
pub fn othermod(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(double, m)?)?;
m.add_class::<ModClass>()?;

View File

@ -12,7 +12,7 @@ fn take_pathbuf(path: PathBuf) -> PathBuf {
}
#[pymodule]
fn path(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
pub fn path(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(make_path, m)?)?;
m.add_function(wrap_pyfunction!(take_pathbuf, m)?)?;

View File

@ -21,7 +21,7 @@ impl pyo3::PyObjectProtocol for Subclassable {
}
#[pymodule]
fn subclassing(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
pub fn subclassing(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_class::<Subclassable>()?;
Ok(())
}

View File

@ -8,8 +8,7 @@ use crate::err::{PyErr, PyResult};
use crate::exceptions::PyTypeError;
use crate::pyclass::PyClass;
use crate::types::{PyAny, PyDict, PyModule, PyString, PyTuple};
use crate::{ffi, PyCell, Python};
use std::cell::UnsafeCell;
use crate::{PyCell, Python};
#[derive(Debug)]
pub struct KeywordOnlyParameterDescription {
@ -297,48 +296,6 @@ impl FunctionDescription {
}
}
/// `Sync` wrapper of `ffi::PyModuleDef`.
pub struct ModuleDef(UnsafeCell<ffi::PyModuleDef>);
unsafe impl Sync for ModuleDef {}
impl ModuleDef {
/// Make new module defenition with given module name.
///
/// # Safety
/// `name` and `doc` must be null-terminated strings.
pub const unsafe fn new(name: &'static str, doc: &'static str) -> Self {
const INIT: ffi::PyModuleDef = ffi::PyModuleDef {
m_base: ffi::PyModuleDef_HEAD_INIT,
m_name: std::ptr::null(),
m_doc: std::ptr::null(),
m_size: 0,
m_methods: std::ptr::null_mut(),
m_slots: std::ptr::null_mut(),
m_traverse: None,
m_clear: None,
m_free: None,
};
ModuleDef(UnsafeCell::new(ffi::PyModuleDef {
m_name: name.as_ptr() as *const _,
m_doc: doc.as_ptr() as *const _,
..INIT
}))
}
/// Builds a module using user given initializer. Used for `#[pymodule]`.
pub fn make_module(
&'static self,
py: Python,
initializer: impl Fn(Python, &PyModule) -> PyResult<()>,
) -> PyResult<*mut ffi::PyObject> {
let module =
unsafe { py.from_owned_ptr_or_err::<PyModule>(ffi::PyModule_Create(self.0.get()))? };
initializer(py, module)?;
Ok(crate::IntoPyPointer::into_ptr(module))
}
}
/// Utility trait to enable &PyClass as a pymethod/function argument
#[doc(hidden)]
pub trait ExtractExt<'a> {

View File

@ -10,3 +10,5 @@ pub mod freelist;
#[doc(hidden)]
pub mod frompyobject;
pub(crate) mod not_send;
#[doc(hidden)]
pub mod pymodule;

149
src/impl_/pymodule.rs Normal file
View File

@ -0,0 +1,149 @@
//! Implementation details of `#[pymodule]` which need to be accessible from proc-macro generated code.
use std::{cell::UnsafeCell, panic::AssertUnwindSafe};
use crate::{
callback::handle_panic, ffi, types::PyModule, IntoPyPointer, Py, PyObject, PyResult, Python,
};
/// `Sync` wrapper of `ffi::PyModuleDef`.
pub struct ModuleDef {
// wrapped in UnsafeCell so that Rust compiler treats this as interior mutability
ffi_def: UnsafeCell<ffi::PyModuleDef>,
initializer: ModuleInitializer,
}
/// Wrapper to enable initializer to be used in const fns.
pub struct ModuleInitializer(pub fn(Python, &PyModule) -> PyResult<()>);
unsafe impl Sync for ModuleDef {}
impl ModuleDef {
/// Make new module definition with given module name.
///
/// # Safety
/// `name` and `doc` must be null-terminated strings.
pub const unsafe fn new(
name: &'static str,
doc: &'static str,
initializer: ModuleInitializer,
) -> Self {
const INIT: ffi::PyModuleDef = ffi::PyModuleDef {
m_base: ffi::PyModuleDef_HEAD_INIT,
m_name: std::ptr::null(),
m_doc: std::ptr::null(),
m_size: 0,
m_methods: std::ptr::null_mut(),
m_slots: std::ptr::null_mut(),
m_traverse: None,
m_clear: None,
m_free: None,
};
let ffi_def = UnsafeCell::new(ffi::PyModuleDef {
m_name: name.as_ptr() as *const _,
m_doc: doc.as_ptr() as *const _,
..INIT
});
ModuleDef {
ffi_def,
initializer,
}
}
/// Builds a module using user given initializer. Used for [`#[pymodule]`][crate::pymodule].
pub fn make_module(&'static self, py: Python) -> PyResult<PyObject> {
let module = unsafe {
Py::<PyModule>::from_owned_ptr_or_err(py, ffi::PyModule_Create(self.ffi_def.get()))?
};
(self.initializer.0)(py, module.as_ref(py))?;
Ok(module.into())
}
/// Implementation of `PyInit_foo` functions generated in [`#[pymodule]`][crate::pymodule]..
///
/// # Safety
/// The Python GIL must be held.
pub unsafe fn module_init(&'static self) -> *mut ffi::PyObject {
let unwind_safe_self = AssertUnwindSafe(self);
handle_panic(|py| Ok(unwind_safe_self.make_module(py)?.into_ptr()))
}
}
#[cfg(test)]
mod tests {
use std::sync::atomic::{AtomicBool, Ordering};
use crate::{types::PyModule, PyResult, Python};
use super::{ModuleDef, ModuleInitializer};
#[test]
fn module_init() {
unsafe {
static MODULE_DEF: ModuleDef = unsafe {
ModuleDef::new(
"test_module\0",
"some doc\0",
ModuleInitializer(|_, m| {
m.add("SOME_CONSTANT", 42)?;
Ok(())
}),
)
};
Python::with_gil(|py| {
let module = MODULE_DEF.module_init();
let pymodule: &PyModule = py.from_owned_ptr(module);
assert_eq!(
pymodule
.getattr("__name__")
.unwrap()
.extract::<&str>()
.unwrap(),
"test_module",
);
assert_eq!(
pymodule
.getattr("__doc__")
.unwrap()
.extract::<&str>()
.unwrap(),
"some doc",
);
assert_eq!(
pymodule
.getattr("SOME_CONSTANT")
.unwrap()
.extract::<u8>()
.unwrap(),
42,
);
})
}
}
#[test]
fn module_def_new() {
// To get coverage for ModuleDef::new() need to create a non-static ModuleDef, however init
// etc require static ModuleDef, so this test needs to be separated out.
static NAME: &str = "test_module\0";
static DOC: &str = "some doc\0";
static INIT_CALLED: AtomicBool = AtomicBool::new(false);
fn init(_: Python, _: &PyModule) -> PyResult<()> {
INIT_CALLED.store(true, Ordering::SeqCst);
Ok(())
}
unsafe {
let module_def: ModuleDef = ModuleDef::new(NAME, DOC, ModuleInitializer(init));
assert_eq!((*module_def.ffi_def.get()).m_name, NAME.as_ptr() as _);
assert_eq!((*module_def.ffi_def.get()).m_doc, DOC.as_ptr() as _);
Python::with_gil(|py| {
module_def.initializer.0(py, py.import("builtins").unwrap()).unwrap();
assert!(INIT_CALLED.load(Ordering::SeqCst));
})
}
}
}

View File

@ -304,7 +304,6 @@ pub use crate::types::PyAny;
#[doc(hidden)]
pub use {
indoc, // Re-exported for py_run
paste, // Re-exported for wrap_function
unindent, // Re-exported for py_run
};
@ -356,8 +355,11 @@ pub mod proc_macro {
}
#[cfg(feature = "macros")]
pub use pyo3_macros::{pyclass, pyfunction, pymethods, pymodule, pyproto, FromPyObject};
pub use pyo3_macros::{
pyclass, pyfunction, pymethods, pymodule, pyproto, wrap_pyfunction, wrap_pymodule, FromPyObject,
};
#[cfg(feature = "macros")]
#[macro_use]
mod macros;

View File

@ -1,33 +1,3 @@
/// Wraps a Rust function annotated with [`#[pyfunction]`](crate::pyfunction), turning it into a
/// [`PyCFunction`](crate::types::PyCFunction).
///
/// This can be used with [`PyModule::add_function`](crate::types::PyModule::add_function) to add
/// free functions to a [`PyModule`](crate::types::PyModule) - see its documention for more information.
#[macro_export]
macro_rules! wrap_pyfunction {
($function_name: ident) => {{
&|py| $crate::paste::expr! { [<__pyo3_get_function_ $function_name>] }(py)
}};
($function_name: ident, $arg: expr) => {
$crate::wrap_pyfunction!($function_name)(
<$crate::derive_utils::PyFunctionArguments as ::std::convert::From<_>>::from($arg),
)
};
}
/// Returns a function that takes a [`Python`](crate::python::Python) instance and returns a Python module.
///
/// Use this together with [`#[pymodule]`](crate::pymodule) and [crate::types::PyModule::add_wrapped].
#[macro_export]
macro_rules! wrap_pymodule {
($module_name:ident) => {{
$crate::paste::expr! {
&|py| unsafe { $crate::PyObject::from_owned_ptr(py, [<PyInit_ $module_name>]()) }
}
}};
}
/// A convenient macro to execute a Python code snippet, with some local variables set.
///
/// # Panics
@ -108,7 +78,6 @@ macro_rules! wrap_pymodule {
/// });
/// ```
#[macro_export]
#[cfg(feature = "macros")]
macro_rules! py_run {
($py:expr, $($val:ident)+, $code:literal) => {{
$crate::py_run_impl!($py, $($val)+, $crate::indoc::indoc!($code))
@ -126,7 +95,6 @@ macro_rules! py_run {
#[macro_export]
#[doc(hidden)]
#[cfg(feature = "macros")]
macro_rules! py_run_impl {
($py:expr, $($val:ident)+, $code:expr) => {{
use $crate::types::IntoPyDict;

View File

@ -20,7 +20,8 @@ pub use crate::pycell::{PyCell, PyRef, PyRefMut};
pub use crate::pyclass_init::PyClassInitializer;
pub use crate::python::Python;
pub use crate::types::{PyAny, PyModule};
pub use crate::wrap_pyfunction;
#[cfg(feature = "macros")]
pub use pyo3_macros::{pyclass, pyfunction, pymethods, pymodule, pyproto, FromPyObject};
pub use pyo3_macros::{
pyclass, pyfunction, pymethods, pymodule, pyproto, wrap_pyfunction, FromPyObject,
};

View File

@ -128,7 +128,7 @@ fn test_module_renaming() {
let gil = Python::acquire_gil();
let py = gil.python();
let d = [("different_name", wrap_pymodule!(other_name)(py))].into_py_dict(py);
let d = [("different_name", wrap_pymodule!(some_name)(py))].into_py_dict(py);
py_run!(py, *d, "assert different_name.__name__ == 'other_name'");
}