Functions not being included in generated type stubs
Bug Description
From https://github.com/PyO3/pyo3/pull/3977
the only introspection data created is the list of function
If I understand this correctly it would mean functions are expected to be added to generated stubs but upon running it myself I don't see any function data being included.
When I try generating stubs for @rules_pyo3//pyo3/private/tests/string_sum/string_sum.rs:
use pyo3::prelude::*;
#[pyfunction]
fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
Ok((a + b).to_string())
}
#[pymodule]
fn string_sum(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
Ok(())
}
the only stubs available are __init__.py (which I expect) but they're empty.
{
"__init__.pyi": "",
}
Steps to Reproduce
- Clone https://github.com/abrisco/rules_pyo3/tree/stubgen
- Run
bazel build //pyo3/private/tests/string_sum:string_sum
Backtrace
Your operating system and version
Linux, MacOS, Windows
Your Python version (python --version)
Python 3.11.11
Your Rust version (rustc --version)
1.85.1 (4eb161250 2025-03-15)
Your PyO3 version
0.25.0-dev
How did you install python? Did you use a virtualenv?
N/A
Additional Info
I know my steps to repro are Bazel but I can work with someone to get more canonical repro steps. My hope was that I could provide enough info so that the issue might be obvious.
cc @Tpt let me know if I just misunderstood the initial implementation! I noticed the pytests don't have functions in them either but perhaps I missed that call out somewhere.
Hey! This is because you use a fn based #[pymodule]. Sadly introspection is based on macros and parsing function code is quite tricky so stubs generation is only working with mod based #[mymodule].
This should work (not tested, I might have made a mistake):
use pyo3::prelude::*;
#[pymodule]
mod string_sum {
use pyo3::prelude::*;
#[pyfunction]
fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
Ok((a + b).to_string())
}
}```
Thanks @Tpt! Easy fix! But I noticed now that the stubs don't seem to have any type info. Left is what I'd expect for the code, right is what was generated. Is there something I'm still forgetting to do?
1c1
< def sum_as_string(a: int, b: int) -> str: ...
---
> def sum_as_string(*args, **kwargs): ...
But I noticed now that the stubs don't seem to have any type info.
yes, this is not implemented yet. The merged MR only implements the basic utilities to extract information via macros, save them in the binaries, extract them from the binaries and write them to the stubs. All complex features (function signatures, class content, type annotations...) will be the topic of follow up MRs (#5025 is implementing function signatures and I have a draft on my disk for type annotations).
With https://github.com/PyO3/pyo3/pull/5025 closed how many more steps do you envision remain for full stubs to be generated?
With https://github.com/PyO3/pyo3/pull/5025 closed how many more steps do you envision remain for full stubs to be generated?
@abrisco I am not sure because I fear surprises. The three major missing points I see are:
- classes (list all the members of a class, supporting getters, setters, magic methods, enums, inheritence...) - likely not very hard but tedious
- type annotations in function signatures - hardest, we need to design it properly and make threadoff, Rust const evaluation is quite restricted
- the ability to extend the generated stubs (adding protocols...) - likely easy if we ignore issues around naming clashes
Edit: a more complete list: #5137
Closing as duplicate of #5137
@Tpt Sorry to ping on an old issue but just looking for clarification. With the release of 0.26.0, I wasn't clear based on #5137 if function stubs were expected to be supported or if an explicit signature is required.
E.g. this (my desire)
use pyo3::prelude::*;
#[pymodule]
mod string_sum {
use super::*;
#[pyfunction]
fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
Ok((a + b).to_string())
}
}
vs this (note the use of text_signature)
use pyo3::prelude::*;
#[pymodule]
mod string_sum {
use super::*;
#[pyfunction]
#[pyo3(text_signature = "(a: int b: int) -> str")]
fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
Ok((a + b).to_string())
}
}
Thanks!
edit: text_signature doesn't seem to do what I want either but maybe I'm missing something. Either way, the docs call out signature but I would love to avoid having to duplicate type information between rust and python if possible.
@abrisco With 0.26, some type annotation should be automatically added (I have not added the needed data to all FromPyObject and IntoPyObject trait implementations hence the "some"). fn sum_as_string(a: usize, b: usize) -> PyResult<String> { should work and give you def sum_as_string(a: int, b: int) -> str. If you want to override some annotations you need to do using the signature attribute e.g. #[pyo3(signature = (a: "int", b) => "str")] (note that you don't have to override annotations for all parameters).
In any case, introspection and stub generation is still a work in progress and a lot of stuff is buggy/not implemented yet.
Fantastic, thank you!