Merge pull request #1650 from davidhewitt/pymodule-name
pymodule: accept `#[pyo3(name = "...")]` option
This commit is contained in:
commit
a290971759
|
@ -49,6 +49,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
- Reduce LLVM line counts to improve compilation times. [#1604](https://github.com/PyO3/pyo3/pull/1604)
|
||||
- Deprecate string-literal second argument to `#[pyfn(m, "name")]`. [#1610](https://github.com/PyO3/pyo3/pull/1610)
|
||||
- No longer call `PyEval_InitThreads()` in `#[pymodule]` init code. [#1630](https://github.com/PyO3/pyo3/pull/1630)
|
||||
- Deprecate `#[pymodule(name)]` option in favor of `#[pyo3(name = "...")]`. [#1650](https://github.com/PyO3/pyo3/pull/1650)
|
||||
- Deprecate `#[text_signature = "..."]` attributes in favor of `#[pyo3(text_signature = "...")]`. [#1658](https://github.com/PyO3/pyo3/pull/1658)
|
||||
- Use `METH_FASTCALL` argument passing convention, when possible, to improve `#[pyfunction]` and method performance.
|
||||
[#1619](https://github.com/PyO3/pyo3/pull/1619), [#1660](https://github.com/PyO3/pyo3/pull/1660)
|
||||
|
|
|
@ -41,8 +41,9 @@ If the name of the module (the default being the function name) does not match t
|
|||
`.pyd` file, you will get an import error in Python with the following message:
|
||||
`ImportError: dynamic module does not define module export function (PyInit_name_of_your_module)`
|
||||
|
||||
To import the module, either copy the shared library as described in [the README](https://github.com/PyO3/pyo3)
|
||||
or use a tool, e.g. `maturin develop` with [maturin](https://github.com/PyO3/maturin) or
|
||||
To import the module, either:
|
||||
- copy the shared library as described in [Manual builds](building_and_distribution.html#manual-builds), or
|
||||
- use a tool, e.g. `maturin develop` with [maturin](https://github.com/PyO3/maturin) or
|
||||
`python setup.py develop` with [setuptools-rust](https://github.com/PyO3/setuptools-rust).
|
||||
|
||||
## Documentation
|
||||
|
|
|
@ -4,6 +4,7 @@ use quote::{quote_spanned, ToTokens};
|
|||
pub enum Deprecation {
|
||||
NameAttribute,
|
||||
PyfnNameArgument,
|
||||
PyModuleNameArgument,
|
||||
TextSignatureAttribute,
|
||||
}
|
||||
|
||||
|
@ -12,6 +13,7 @@ impl Deprecation {
|
|||
let string = match self {
|
||||
Deprecation::NameAttribute => "NAME_ATTRIBUTE",
|
||||
Deprecation::PyfnNameArgument => "PYFN_NAME_ARGUMENT",
|
||||
Deprecation::PyModuleNameArgument => "PYMODULE_NAME_ARGUMENT",
|
||||
Deprecation::TextSignatureAttribute => "TEXT_SIGNATURE_ATTRIBUTE",
|
||||
};
|
||||
syn::Ident::new(string, span)
|
||||
|
|
|
@ -24,7 +24,7 @@ mod pymethod;
|
|||
mod pyproto;
|
||||
|
||||
pub use from_pyobject::build_derive_from_pyobject;
|
||||
pub use module::{process_functions_in_module, py_init};
|
||||
pub use module::{process_functions_in_module, py_init, PyModuleOptions};
|
||||
pub use pyclass::{build_py_class, PyClassArgs};
|
||||
pub use pyfunction::{build_py_function, PyFunctionOptions};
|
||||
pub use pyimpl::{build_py_methods, PyClassMethodsType};
|
||||
|
|
|
@ -1,18 +1,70 @@
|
|||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
||||
//! Code generation for the function that initializes a python module and adds classes and function.
|
||||
|
||||
use crate::pyfunction::{impl_wrap_pyfunction, PyFunctionOptions};
|
||||
use crate::{
|
||||
attributes::{self, take_pyo3_options},
|
||||
deprecations::Deprecations,
|
||||
pyfunction::{impl_wrap_pyfunction, PyFunctionOptions},
|
||||
};
|
||||
use crate::{
|
||||
attributes::{is_attribute_ident, take_attributes, NameAttribute},
|
||||
deprecations::Deprecation,
|
||||
};
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::quote;
|
||||
use syn::{parse::Parse, spanned::Spanned, token::Comma, Ident, Path};
|
||||
use syn::{
|
||||
ext::IdentExt,
|
||||
parse::{Parse, ParseStream},
|
||||
spanned::Spanned,
|
||||
token::Comma,
|
||||
Ident, Path, Result,
|
||||
};
|
||||
|
||||
pub struct PyModuleOptions {
|
||||
name: Option<syn::Ident>,
|
||||
deprecations: Deprecations,
|
||||
}
|
||||
|
||||
impl PyModuleOptions {
|
||||
pub fn from_pymodule_arg_and_attrs(
|
||||
deprecated_pymodule_name_arg: Option<syn::Ident>,
|
||||
attrs: &mut Vec<syn::Attribute>,
|
||||
) -> Result<Self> {
|
||||
let mut deprecations = Deprecations::new();
|
||||
if let Some(name) = &deprecated_pymodule_name_arg {
|
||||
deprecations.push(Deprecation::PyModuleNameArgument, name.span());
|
||||
}
|
||||
|
||||
let mut options: PyModuleOptions = PyModuleOptions {
|
||||
name: deprecated_pymodule_name_arg,
|
||||
deprecations,
|
||||
};
|
||||
|
||||
for option in take_pyo3_options(attrs)? {
|
||||
match option {
|
||||
PyModulePyO3Option::Name(name) => options.set_name(name.0)?,
|
||||
}
|
||||
}
|
||||
|
||||
Ok(options)
|
||||
}
|
||||
|
||||
fn set_name(&mut self, name: syn::Ident) -> Result<()> {
|
||||
ensure_spanned!(
|
||||
self.name.is_none(),
|
||||
name.span() => "`name` may only be specified once"
|
||||
);
|
||||
|
||||
self.name = Some(name);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates the function that is called by the python interpreter to initialize the native
|
||||
/// module
|
||||
pub fn py_init(fnname: &Ident, name: &Ident, doc: syn::LitStr) -> TokenStream {
|
||||
pub fn py_init(fnname: &Ident, options: PyModuleOptions, doc: syn::LitStr) -> TokenStream {
|
||||
let name = options.name.unwrap_or_else(|| fnname.unraw());
|
||||
let deprecations = options.deprecations;
|
||||
let cb_name = Ident::new(&format!("PyInit_{}", name), Span::call_site());
|
||||
assert!(doc.value().ends_with('\0'));
|
||||
|
||||
|
@ -27,6 +79,8 @@ pub fn py_init(fnname: &Ident, name: &Ident, doc: syn::LitStr) -> TokenStream {
|
|||
static DOC: &str = #doc;
|
||||
static MODULE_DEF: ModuleDef = unsafe { ModuleDef::new(NAME, DOC) };
|
||||
|
||||
#deprecations
|
||||
|
||||
pyo3::callback::handle_panic(|_py| { MODULE_DEF.make_module(_py, #fnname) })
|
||||
}
|
||||
}
|
||||
|
@ -36,21 +90,19 @@ pub fn py_init(fnname: &Ident, name: &Ident, doc: syn::LitStr) -> TokenStream {
|
|||
pub fn process_functions_in_module(func: &mut syn::ItemFn) -> syn::Result<()> {
|
||||
let mut stmts: Vec<syn::Stmt> = Vec::new();
|
||||
|
||||
for stmt in func.block.stmts.iter_mut() {
|
||||
if let syn::Stmt::Item(syn::Item::Fn(func)) = stmt {
|
||||
for mut stmt in func.block.stmts.drain(..) {
|
||||
if let syn::Stmt::Item(syn::Item::Fn(func)) = &mut stmt {
|
||||
if let Some(pyfn_args) = get_pyfn_attr(&mut func.attrs)? {
|
||||
let module_name = pyfn_args.modname;
|
||||
let (ident, wrapped_function) = impl_wrap_pyfunction(func, pyfn_args.options)?;
|
||||
let item: syn::ItemFn = syn::parse_quote! {
|
||||
fn block_wrapper() {
|
||||
#wrapped_function
|
||||
#module_name.add_function(#ident(#module_name)?)?;
|
||||
}
|
||||
let statements: Vec<syn::Stmt> = syn::parse_quote! {
|
||||
#wrapped_function
|
||||
#module_name.add_function(#ident(#module_name)?)?;
|
||||
};
|
||||
stmts.extend(item.block.stmts.into_iter());
|
||||
stmts.extend(statements);
|
||||
}
|
||||
};
|
||||
stmts.push(stmt.clone());
|
||||
stmts.push(stmt);
|
||||
}
|
||||
|
||||
func.block.stmts = stmts;
|
||||
|
@ -120,3 +172,18 @@ fn get_pyfn_attr(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Option<PyFnArgs
|
|||
|
||||
Ok(pyfn_args)
|
||||
}
|
||||
|
||||
enum PyModulePyO3Option {
|
||||
Name(NameAttribute),
|
||||
}
|
||||
|
||||
impl Parse for PyModulePyO3Option {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let lookahead = input.lookahead1();
|
||||
if lookahead.peek(attributes::kw::name) {
|
||||
input.parse().map(PyModulePyO3Option::Name)
|
||||
} else {
|
||||
Err(lookahead.error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,23 +9,43 @@ use proc_macro::TokenStream;
|
|||
use pyo3_macros_backend::{
|
||||
build_derive_from_pyobject, build_py_class, build_py_function, build_py_methods,
|
||||
build_py_proto, get_doc, process_functions_in_module, py_init, PyClassArgs, PyClassMethodsType,
|
||||
PyFunctionOptions,
|
||||
PyFunctionOptions, PyModuleOptions,
|
||||
};
|
||||
use quote::quote;
|
||||
use syn::parse_macro_input;
|
||||
|
||||
/// A proc macro used to implement Python modules.
|
||||
///
|
||||
/// For more on creating Python modules
|
||||
/// see the [module section of the guide](https://pyo3.rs/main/module.html).
|
||||
/// The name of the module will be taken from the function name, unless `#[pyo3(name = "my_name")]`
|
||||
/// is also annotated on the function to override the name. **Important**: the module name should
|
||||
/// match the `lib.name` setting in `Cargo.toml`, so that Python is able to import the module
|
||||
/// without needing a custom import loader.
|
||||
///
|
||||
/// Functions annotated with `#[pymodule]` can also be annotated with the following:
|
||||
///
|
||||
/// | Annotation | Description |
|
||||
/// | :- | :- |
|
||||
/// | `#[pyo3(name = "...")]` | Defines the name of the module in Python. |
|
||||
///
|
||||
/// For more on creating Python modules see the [module section of the guide][1].
|
||||
///
|
||||
/// [1]: https://pyo3.rs/main/module.html
|
||||
#[proc_macro_attribute]
|
||||
pub fn pymodule(attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||
let mut ast = parse_macro_input!(input as syn::ItemFn);
|
||||
|
||||
let modname = if attr.is_empty() {
|
||||
ast.sig.ident.clone()
|
||||
let deprecated_pymodule_name_arg = if attr.is_empty() {
|
||||
None
|
||||
} else {
|
||||
parse_macro_input!(attr as syn::Ident)
|
||||
Some(parse_macro_input!(attr as syn::Ident))
|
||||
};
|
||||
|
||||
let options = match PyModuleOptions::from_pymodule_arg_and_attrs(
|
||||
deprecated_pymodule_name_arg,
|
||||
&mut ast.attrs,
|
||||
) {
|
||||
Ok(options) => options,
|
||||
Err(e) => return e.to_compile_error().into(),
|
||||
};
|
||||
|
||||
if let Err(err) = process_functions_in_module(&mut ast) {
|
||||
|
@ -37,7 +57,7 @@ pub fn pymodule(attr: TokenStream, input: TokenStream) -> TokenStream {
|
|||
Err(err) => return err.to_compile_error().into(),
|
||||
};
|
||||
|
||||
let expanded = py_init(&ast.sig.ident, &modname, doc);
|
||||
let expanded = py_init(&ast.sig.ident, options, doc);
|
||||
|
||||
quote!(
|
||||
#ast
|
||||
|
|
|
@ -12,6 +12,12 @@ pub const NAME_ATTRIBUTE: () = ();
|
|||
)]
|
||||
pub const PYFN_NAME_ARGUMENT: () = ();
|
||||
|
||||
#[deprecated(
|
||||
since = "0.14.0",
|
||||
note = "use `#[pymodule] #[pyo3(name = \"...\")]` instead of `#[pymodule(...)]`"
|
||||
)]
|
||||
pub const PYMODULE_NAME_ARGUMENT: () = ();
|
||||
|
||||
#[deprecated(
|
||||
since = "0.14.0",
|
||||
note = "use `#[pyo3(text_signature = \"...\")]` instead of `#[text_signature = \"...\"]`"
|
||||
|
|
|
@ -123,7 +123,8 @@ fn test_module_with_functions() {
|
|||
);
|
||||
}
|
||||
|
||||
#[pymodule(other_name)]
|
||||
#[pymodule]
|
||||
#[pyo3(name = "other_name")]
|
||||
fn some_name(_: Python, m: &PyModule) -> PyResult<()> {
|
||||
m.add("other_name", "other_name")?;
|
||||
Ok(())
|
||||
|
@ -436,3 +437,17 @@ fn test_module_functions_with_module() {
|
|||
"m.pyfunction_with_pass_module_in_attribute() == 'module_with_functions_with_module'"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(deprecated)]
|
||||
fn test_module_with_deprecated_name() {
|
||||
#[pymodule(custom_name)]
|
||||
fn my_module(_py: Python, _m: &PyModule) -> PyResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Python::with_gil(|py| {
|
||||
let m = pyo3::wrap_pymodule!(custom_name)(py);
|
||||
py_assert!(py, m, "m.__name__ == 'custom_name'");
|
||||
})
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ impl TestClass {
|
|||
#[text_signature = "()"]
|
||||
fn deprecated_name_pyfunction() { }
|
||||
|
||||
#[pymodule]
|
||||
#[pymodule(deprecated_module_name)]
|
||||
fn my_module(_py: Python, m: &PyModule) -> PyResult<()> {
|
||||
#[pyfn(m, "some_name")]
|
||||
#[text_signature = "()"]
|
||||
|
|
|
@ -58,6 +58,12 @@ error: use of deprecated constant `pyo3::impl_::deprecations::TEXT_SIGNATURE_ATT
|
|||
35 | #[text_signature = "()"]
|
||||
| ^
|
||||
|
||||
error: use of deprecated constant `pyo3::impl_::deprecations::PYMODULE_NAME_ARGUMENT`: use `#[pymodule] #[pyo3(name = "...")]` instead of `#[pymodule(...)]`
|
||||
--> $DIR/deprecations.rs:32:12
|
||||
|
|
||||
32 | #[pymodule(deprecated_module_name)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: use of deprecated constant `pyo3::impl_::deprecations::TEXT_SIGNATURE_ATTRIBUTE`: use `#[pyo3(text_signature = "...")]` instead of `#[text_signature = "..."]`
|
||||
--> $DIR/deprecations.rs:6:1
|
||||
|
|
||||
|
|
Loading…
Reference in a new issue