diff --git a/Cargo.toml b/Cargo.toml index 5386b76f573..78ab72525a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,6 +69,9 @@ default = ["macros"] # and IntoPy traits experimental-inspect = [] +# Enables annotating Rust inline modules with #[pymodule] to build Python modules declaratively +experimental-declarative-modules = ["pyo3-macros/experimental-declarative-modules"] + # Enables macros: #[pyclass], #[pymodule], #[pyfunction] etc. macros = ["pyo3-macros", "indoc", "unindent"] diff --git a/newsfragments/3815.added.md b/newsfragments/3815.added.md index dd450ca88c6..e4fd3e9315a 100644 --- a/newsfragments/3815.added.md +++ b/newsfragments/3815.added.md @@ -1 +1,2 @@ -The ability to create Python modules with a Rust `mod` block. \ No newline at end of file +The ability to create Python modules with a Rust `mod` block +behind the `experimental-declarative-modules` feature. \ No newline at end of file diff --git a/pyo3-macros/Cargo.toml b/pyo3-macros/Cargo.toml index 576c94a2bc1..a0368a5f364 100644 --- a/pyo3-macros/Cargo.toml +++ b/pyo3-macros/Cargo.toml @@ -15,6 +15,7 @@ proc-macro = true [features] multiple-pymethods = [] +experimental-declarative-modules = [] [dependencies] proc-macro2 = { version = "1", default-features = false } diff --git a/pyo3-macros/src/lib.rs b/pyo3-macros/src/lib.rs index 8dbf2782d5b..098b23e709f 100644 --- a/pyo3-macros/src/lib.rs +++ b/pyo3-macros/src/lib.rs @@ -37,7 +37,7 @@ use syn::{parse::Nothing, parse_macro_input, Item}; pub fn pymodule(args: TokenStream, input: TokenStream) -> TokenStream { parse_macro_input!(args as Nothing); match parse_macro_input!(input as Item) { - Item::Mod(module) => pymodule_module_impl(module), + Item::Mod(module) if cfg!(feature = "experimental-declarative-modules") => pymodule_module_impl(module), Item::Fn(function) => pymodule_function_impl(function), unsupported => Err(syn::Error::new_spanned( unsupported, diff --git a/pytests/src/lib.rs b/pytests/src/lib.rs index a5ee6b20b7b..85cf435d1d9 100644 --- a/pytests/src/lib.rs +++ b/pytests/src/lib.rs @@ -1,4 +1,6 @@ use pyo3::prelude::*; +use pyo3::types::PyDict; +use pyo3::wrap_pymodule; pub mod awaitable; pub mod buf_and_str; @@ -16,39 +18,43 @@ pub mod sequence; pub mod subclassing; #[pymodule] -mod pyo3_pytests { - use super::*; - use pyo3::types::PyDict; - #[pymodule_export] - use { - awaitable::awaitable, comparisons::comparisons, dict_iter::dict_iter, enums::enums, - misc::misc, objstore::objstore, othermod::othermod, path::path, pyclasses::pyclasses, - pyfunctions::pyfunctions, sequence::sequence, subclassing::subclassing, - }; +fn pyo3_pytests(py: Python<'_>, m: &PyModule) -> PyResult<()> { + m.add_wrapped(wrap_pymodule!(awaitable::awaitable))?; #[cfg(not(Py_LIMITED_API))] - #[pymodule_export] - use {buf_and_str::buf_and_str, datetime::datetime}; + m.add_wrapped(wrap_pymodule!(buf_and_str::buf_and_str))?; + m.add_wrapped(wrap_pymodule!(comparisons::comparisons))?; + #[cfg(not(Py_LIMITED_API))] + m.add_wrapped(wrap_pymodule!(datetime::datetime))?; + m.add_wrapped(wrap_pymodule!(dict_iter::dict_iter))?; + m.add_wrapped(wrap_pymodule!(enums::enums))?; + 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!(pyclasses::pyclasses))?; + m.add_wrapped(wrap_pymodule!(pyfunctions::pyfunctions))?; + m.add_wrapped(wrap_pymodule!(sequence::sequence))?; + 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 + + let sys = PyModule::import(py, "sys")?; + let sys_modules: &PyDict = sys.getattr("modules")?.downcast()?; + sys_modules.set_item("pyo3_pytests.awaitable", m.getattr("awaitable")?)?; + sys_modules.set_item("pyo3_pytests.buf_and_str", m.getattr("buf_and_str")?)?; + sys_modules.set_item("pyo3_pytests.comparisons", m.getattr("comparisons")?)?; + sys_modules.set_item("pyo3_pytests.datetime", m.getattr("datetime")?)?; + sys_modules.set_item("pyo3_pytests.dict_iter", m.getattr("dict_iter")?)?; + sys_modules.set_item("pyo3_pytests.enums", m.getattr("enums")?)?; + sys_modules.set_item("pyo3_pytests.misc", m.getattr("misc")?)?; + sys_modules.set_item("pyo3_pytests.objstore", m.getattr("objstore")?)?; + sys_modules.set_item("pyo3_pytests.othermod", m.getattr("othermod")?)?; + sys_modules.set_item("pyo3_pytests.path", m.getattr("path")?)?; + sys_modules.set_item("pyo3_pytests.pyclasses", m.getattr("pyclasses")?)?; + sys_modules.set_item("pyo3_pytests.pyfunctions", m.getattr("pyfunctions")?)?; + sys_modules.set_item("pyo3_pytests.sequence", m.getattr("sequence")?)?; + sys_modules.set_item("pyo3_pytests.subclassing", m.getattr("subclassing")?)?; - #[pymodule_init] - fn init(m: &Bound<'_, PyModule>) -> PyResult<()> { - // Inserting to sys.modules allows importing submodules nicely from Python - // e.g. import pyo3_pytests.buf_and_str as bas - let sys = PyModule::import(m.py(), "sys")?; - let sys_modules: &PyDict = sys.getattr("modules")?.downcast()?; - sys_modules.set_item("pyo3_pytests.awaitable", m.getattr("awaitable")?)?; - sys_modules.set_item("pyo3_pytests.buf_and_str", m.getattr("buf_and_str")?)?; - sys_modules.set_item("pyo3_pytests.comparisons", m.getattr("comparisons")?)?; - sys_modules.set_item("pyo3_pytests.datetime", m.getattr("datetime")?)?; - sys_modules.set_item("pyo3_pytests.dict_iter", m.getattr("dict_iter")?)?; - sys_modules.set_item("pyo3_pytests.enums", m.getattr("enums")?)?; - sys_modules.set_item("pyo3_pytests.misc", m.getattr("misc")?)?; - sys_modules.set_item("pyo3_pytests.objstore", m.getattr("objstore")?)?; - sys_modules.set_item("pyo3_pytests.othermod", m.getattr("othermod")?)?; - sys_modules.set_item("pyo3_pytests.path", m.getattr("path")?)?; - sys_modules.set_item("pyo3_pytests.pyclasses", m.getattr("pyclasses")?)?; - sys_modules.set_item("pyo3_pytests.pyfunctions", m.getattr("pyfunctions")?)?; - sys_modules.set_item("pyo3_pytests.sequence", m.getattr("sequence")?)?; - sys_modules.set_item("pyo3_pytests.subclassing", m.getattr("subclassing")?)?; - Ok(()) - } -} + Ok(()) +} \ No newline at end of file diff --git a/tests/test_append_to_inittab.rs b/tests/test_append_to_inittab.rs index 06ecc0ef893..eb812433585 100644 --- a/tests/test_append_to_inittab.rs +++ b/tests/test_append_to_inittab.rs @@ -13,6 +13,7 @@ fn module_fn_with_functions(_py: Python<'_>, m: &PyModule) -> PyResult<()> { Ok(()) } +#[cfg(feature = "experimental-declarative-modules")] #[pymodule] mod module_mod_with_functions { #[pymodule_export] @@ -24,7 +25,6 @@ mod module_mod_with_functions { fn test_module_append_to_inittab() { use pyo3::append_to_inittab; append_to_inittab!(module_fn_with_functions); - append_to_inittab!(module_mod_with_functions); Python::with_gil(|py| { py.run_bound( r#" @@ -37,6 +37,10 @@ assert module_fn_with_functions.foo() == 123 .map_err(|e| e.display(py)) .unwrap(); }); + + #[cfg(feature = "experimental-declarative-modules")] + append_to_inittab!(module_mod_with_functions); + #[cfg(feature = "experimental-declarative-modules")] Python::with_gil(|py| { py.run_bound( r#" diff --git a/tests/test_module.rs b/tests/test_module.rs index 6944fd1318e..31e7c1f0a53 100644 --- a/tests/test_module.rs +++ b/tests/test_module.rs @@ -494,6 +494,7 @@ create_exception!( ); /// A module written using declarative syntax. +#[cfg(feature = "experimental-declarative-modules")] #[pymodule] mod declarative_module { #[pymodule_export] @@ -512,11 +513,13 @@ mod declarative_module { } } +#[cfg(feature = "experimental-declarative-modules")] #[pyfunction] fn double_value(v: &ValueClass) -> usize { v.value * 2 } +#[cfg(feature = "experimental-declarative-modules")] #[pymodule] mod declarative_submodule { #[pymodule_export] @@ -524,6 +527,7 @@ mod declarative_submodule { } /// A module written using declarative syntax. +#[cfg(feature = "experimental-declarative-modules")] #[pymodule] #[pyo3(name = "declarative_module_renamed")] mod declarative_module2 { @@ -531,6 +535,7 @@ mod declarative_module2 { use super::double; } +#[cfg(feature = "experimental-declarative-modules")] #[test] fn test_declarative_module() { Python::with_gil(|py| {