Skip to content

Commit

Permalink
Gates #[pymodule] mod behind the experimental-declarative-modules fea…
Browse files Browse the repository at this point in the history
…ture
  • Loading branch information
Tpt committed Feb 21, 2024
1 parent 78fd5be commit e75c50a
Show file tree
Hide file tree
Showing 8 changed files with 67 additions and 36 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]

Expand Down
3 changes: 2 additions & 1 deletion newsfragments/3815.added.md
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
The ability to create Python modules with a Rust `mod` block.
The ability to create Python modules with a Rust `mod` block
behind the `experimental-declarative-modules` feature.
1 change: 1 addition & 0 deletions pyo3-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ proc-macro = true

[features]
multiple-pymethods = []
experimental-declarative-modules = []

[dependencies]
proc-macro2 = { version = "1", default-features = false }
Expand Down
9 changes: 8 additions & 1 deletion pyo3-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,14 @@ 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)
} else {
Err(syn::Error::new_spanned(
module,
"#[pymodule] requires the 'experimental-declarative-modules' feature to be used on Rust modules.",
))
},
Item::Fn(function) => pymodule_function_impl(function),
unsupported => Err(syn::Error::new_spanned(
unsupported,
Expand Down
72 changes: 39 additions & 33 deletions pytests/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use pyo3::prelude::*;
use pyo3::types::PyDict;
use pyo3::wrap_pymodule;

pub mod awaitable;
pub mod buf_and_str;
Expand All @@ -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(())
}
6 changes: 5 additions & 1 deletion tests/test_append_to_inittab.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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#"
Expand All @@ -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#"
Expand Down
4 changes: 4 additions & 0 deletions tests/test_compile_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,12 @@ fn test_compile_errors() {
t.compile_fail("tests/ui/not_send2.rs");
t.compile_fail("tests/ui/get_set_all.rs");
t.compile_fail("tests/ui/traverse.rs");
#[cfg(feature = "experimental-declarative-modules")]
t.compile_fail("tests/ui/invalid_pymodule_in_root.rs");
#[cfg(feature = "experimental-declarative-modules")]
t.compile_fail("tests/ui/invalid_pymodule_glob.rs");
#[cfg(feature = "experimental-declarative-modules")]
t.compile_fail("tests/ui/invalid_pymodule_trait.rs");
#[cfg(feature = "experimental-declarative-modules")]
t.compile_fail("tests/ui/invalid_pymodule_two_pymodule_init.rs");
}
5 changes: 5 additions & 0 deletions tests/test_module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,7 @@ create_exception!(
);

/// A module written using declarative syntax.
#[cfg(feature = "experimental-declarative-modules")]
#[pymodule]
mod declarative_module {
#[pymodule_export]
Expand All @@ -512,25 +513,29 @@ 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]
use super::{double, double_value};
}

/// A module written using declarative syntax.
#[cfg(feature = "experimental-declarative-modules")]
#[pymodule]
#[pyo3(name = "declarative_module_renamed")]
mod declarative_module2 {
#[pymodule_export]
use super::double;
}

#[cfg(feature = "experimental-declarative-modules")]
#[test]
fn test_declarative_module() {
Python::with_gil(|py| {
Expand Down

0 comments on commit e75c50a

Please sign in to comment.