pyo3 icon indicating copy to clipboard operation
pyo3 copied to clipboard

Functions not being included in generated type stubs

Open abrisco opened this issue 8 months ago • 6 comments

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

  1. Clone https://github.com/abrisco/rules_pyo3/tree/stubgen
  2. 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.

abrisco avatar Apr 02 '25 14:04 abrisco

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.

abrisco avatar Apr 02 '25 14:04 abrisco

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())
    }
}```

Tpt avatar Apr 02 '25 14:04 Tpt

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): ...

abrisco avatar Apr 02 '25 14:04 abrisco

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).

Tpt avatar Apr 02 '25 15:04 Tpt

With https://github.com/PyO3/pyo3/pull/5025 closed how many more steps do you envision remain for full stubs to be generated?

abrisco avatar Apr 21 '25 16:04 abrisco

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

Tpt avatar Apr 22 '25 07:04 Tpt

Closing as duplicate of #5137

davidhewitt avatar Jul 19 '25 09:07 davidhewitt

@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 avatar Sep 03 '25 01:09 abrisco

@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.

Tpt avatar Sep 03 '25 06:09 Tpt

Fantastic, thank you!

abrisco avatar Sep 03 '25 19:09 abrisco