Skip to content

Commit dd1f788

Browse files
authored
add declarative module level constants export (PyO3#5096)
1 parent 7a7b77b commit dd1f788

File tree

6 files changed

+59
-19
lines changed

6 files changed

+59
-19
lines changed

guide/src/module.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,9 @@ mod my_extension {
124124
#[pymodule_export]
125125
use super::double; // Exports the double function as part of the module
126126
127+
#[pymodule_export]
128+
const PI: f64 = std::f64::consts::PI; // Exports PI constant as part of the module
129+
127130
#[pyfunction] // This will be part of the module
128131
fn triple(x: usize) -> usize {
129132
x * 3

newsfragments/5096.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
declarative modules can now `#[pymodule_export]` `const` items

pyo3-macros-backend/src/module.rs

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ pub fn pymodule_module_impl(
149149
}
150150

151151
let mut pymodule_init = None;
152+
let mut module_consts = Vec::new();
153+
let mut module_consts_cfg_attrs = Vec::new();
152154

153155
for item in &mut *items {
154156
match item {
@@ -168,7 +170,7 @@ pub fn pymodule_module_impl(
168170
Item::Fn(item_fn) => {
169171
ensure_spanned!(
170172
!has_attribute(&item_fn.attrs, "pymodule_export"),
171-
item.span() => "`#[pymodule_export]` may only be used on `use` statements"
173+
item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements"
172174
);
173175
let is_pymodule_init =
174176
find_and_remove_attribute(&mut item_fn.attrs, "pymodule_init");
@@ -199,7 +201,7 @@ pub fn pymodule_module_impl(
199201
Item::Struct(item_struct) => {
200202
ensure_spanned!(
201203
!has_attribute(&item_struct.attrs, "pymodule_export"),
202-
item.span() => "`#[pymodule_export]` may only be used on `use` statements"
204+
item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements"
203205
);
204206
if has_attribute(&item_struct.attrs, "pyclass")
205207
|| has_attribute_with_namespace(
@@ -227,7 +229,7 @@ pub fn pymodule_module_impl(
227229
Item::Enum(item_enum) => {
228230
ensure_spanned!(
229231
!has_attribute(&item_enum.attrs, "pymodule_export"),
230-
item.span() => "`#[pymodule_export]` may only be used on `use` statements"
232+
item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements"
231233
);
232234
if has_attribute(&item_enum.attrs, "pyclass")
233235
|| has_attribute_with_namespace(&item_enum.attrs, Some(pyo3_path), &["pyclass"])
@@ -251,7 +253,7 @@ pub fn pymodule_module_impl(
251253
Item::Mod(item_mod) => {
252254
ensure_spanned!(
253255
!has_attribute(&item_mod.attrs, "pymodule_export"),
254-
item.span() => "`#[pymodule_export]` may only be used on `use` statements"
256+
item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements"
255257
);
256258
if has_attribute(&item_mod.attrs, "pymodule")
257259
|| has_attribute_with_namespace(&item_mod.attrs, Some(pyo3_path), &["pymodule"])
@@ -278,61 +280,63 @@ pub fn pymodule_module_impl(
278280
Item::ForeignMod(item) => {
279281
ensure_spanned!(
280282
!has_attribute(&item.attrs, "pymodule_export"),
281-
item.span() => "`#[pymodule_export]` may only be used on `use` statements"
283+
item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements"
282284
);
283285
}
284286
Item::Trait(item) => {
285287
ensure_spanned!(
286288
!has_attribute(&item.attrs, "pymodule_export"),
287-
item.span() => "`#[pymodule_export]` may only be used on `use` statements"
289+
item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements"
288290
);
289291
}
290292
Item::Const(item) => {
291-
ensure_spanned!(
292-
!has_attribute(&item.attrs, "pymodule_export"),
293-
item.span() => "`#[pymodule_export]` may only be used on `use` statements"
294-
);
293+
if !find_and_remove_attribute(&mut item.attrs, "pymodule_export") {
294+
continue;
295+
}
296+
297+
module_consts.push(item.ident.clone());
298+
module_consts_cfg_attrs.push(get_cfg_attributes(&item.attrs));
295299
}
296300
Item::Static(item) => {
297301
ensure_spanned!(
298302
!has_attribute(&item.attrs, "pymodule_export"),
299-
item.span() => "`#[pymodule_export]` may only be used on `use` statements"
303+
item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements"
300304
);
301305
}
302306
Item::Macro(item) => {
303307
ensure_spanned!(
304308
!has_attribute(&item.attrs, "pymodule_export"),
305-
item.span() => "`#[pymodule_export]` may only be used on `use` statements"
309+
item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements"
306310
);
307311
}
308312
Item::ExternCrate(item) => {
309313
ensure_spanned!(
310314
!has_attribute(&item.attrs, "pymodule_export"),
311-
item.span() => "`#[pymodule_export]` may only be used on `use` statements"
315+
item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements"
312316
);
313317
}
314318
Item::Impl(item) => {
315319
ensure_spanned!(
316320
!has_attribute(&item.attrs, "pymodule_export"),
317-
item.span() => "`#[pymodule_export]` may only be used on `use` statements"
321+
item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements"
318322
);
319323
}
320324
Item::TraitAlias(item) => {
321325
ensure_spanned!(
322326
!has_attribute(&item.attrs, "pymodule_export"),
323-
item.span() => "`#[pymodule_export]` may only be used on `use` statements"
327+
item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements"
324328
);
325329
}
326330
Item::Type(item) => {
327331
ensure_spanned!(
328332
!has_attribute(&item.attrs, "pymodule_export"),
329-
item.span() => "`#[pymodule_export]` may only be used on `use` statements"
333+
item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements"
330334
);
331335
}
332336
Item::Union(item) => {
333337
ensure_spanned!(
334338
!has_attribute(&item.attrs, "pymodule_export"),
335-
item.span() => "`#[pymodule_export]` may only be used on `use` statements"
339+
item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements"
336340
);
337341
}
338342
_ => (),
@@ -372,6 +376,8 @@ pub fn pymodule_module_impl(
372376
options.gil_used.map_or(true, |op| op.value.value),
373377
);
374378

379+
let module_consts_names = module_consts.iter().map(|i| i.unraw().to_string());
380+
375381
Ok(quote!(
376382
#(#attrs)*
377383
#vis #mod_token #ident {
@@ -387,6 +393,12 @@ pub fn pymodule_module_impl(
387393
#(#module_items_cfg_attrs)*
388394
#module_items::_PYO3_DEF.add_to_module(module)?;
389395
)*
396+
397+
#(
398+
#(#module_consts_cfg_attrs)*
399+
#pyo3_path::types::PyModuleMethods::add(module, #module_consts_names, #module_consts)?;
400+
)*
401+
390402
#pymodule_init
391403
::std::result::Result::Ok(())
392404
}

src/tests/hygiene/pymodule.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,7 @@ fn my_module(m: &crate::Bound<'_, crate::types::PyModule>) -> crate::PyResult<()
3333
mod my_module_declarative {
3434
#[pymodule_export]
3535
use super::{do_something, foo};
36+
37+
#[pymodule_export]
38+
const BAR: u32 = 42;
3639
}

tests/test_declarative_module.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,20 @@ mod declarative_module {
7676
#[pymodule_export]
7777
use super::external_submodule;
7878

79+
#[pymodule_export]
80+
const FOO: u32 = 42;
81+
82+
#[pymodule_export]
83+
#[cfg(Py_LIMITED_API)]
84+
const BAR: &str = "BAR";
85+
86+
#[pymodule_export]
87+
#[allow(non_upper_case_globals)]
88+
const r#type: char = '!';
89+
90+
#[allow(unused)]
91+
const NOT_EXPORTED: &str = "not exported";
92+
7993
#[pymodule]
8094
mod inner {
8195
use super::*;
@@ -190,7 +204,14 @@ fn test_declarative_module() {
190204
py_assert!(py, m, "hasattr(m, 'LocatedClass')");
191205
py_assert!(py, m, "isinstance(m.inner.Struct(), m.inner.Struct)");
192206
py_assert!(py, m, "isinstance(m.inner.Enum.A, m.inner.Enum)");
193-
py_assert!(py, m, "hasattr(m, 'external_submodule')")
207+
py_assert!(py, m, "hasattr(m, 'external_submodule')");
208+
py_assert!(py, m, "m.FOO == 42");
209+
#[cfg(Py_LIMITED_API)]
210+
py_assert!(py, m, "m.BAR == 'BAR'");
211+
#[cfg(not(Py_LIMITED_API))]
212+
py_assert!(py, m, "not hasattr(m, 'BAR')");
213+
py_assert!(py, m, "m.type == '!'");
214+
py_assert!(py, m, "not hasattr(m, 'NOT_EXPORTED')");
194215
})
195216
}
196217

tests/ui/invalid_pymodule_trait.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
error: `#[pymodule_export]` may only be used on `use` statements
1+
error: `#[pymodule_export]` may only be used on `use` or `const` statements
22
--> tests/ui/invalid_pymodule_trait.rs:5:5
33
|
44
5 | #[pymodule_export]

0 commit comments

Comments
 (0)