Type stub generation (*.pyi files) and introspection
This feature is still in active development. See the related issue.
For documentation on type stubs and how to use them with stable PyO3, refer to this page
PyO3 has a work in progress support to generate type stub files.
It works using:
- PyO3 macros (
#[pyclass]) that generate constant JSON strings that are then included in the built binaries by rustc if theexperimental-inspectfeature is enabled. - The
pyo3-introspectioncrate that can parse the generated binaries, extract the JSON strings and build stub files from it. - Build tools like
maturinexposing options in their CLI API to generate the stubs file.
For example, the following Rust code
#[pymodule]
pub mod example {
use pyo3::prelude::*;
#[pymodule_export]
pub const CONSTANT: &str = "FOO";
#[pyclass(eq)]
#[derive(Eq)]
struct Class {
value: usize
}
#[pymethods]
impl Class {
#[new]
fn new(value: usize) -> Self {
Self { value }
}
#[getter]
fn value(&self) -> usize {
self.value
}
}
#[pyfunction]
#[pyo3(signature = (arg: "list[int]") -> "list[int]")]
fn list_of_int_identity(arg: Bound<'_, PyAny>) -> Bound<'_, PyAny> {
arg
}
}
will generate the following stub file:
import typing
CONSTANT: typing.Final = "FOO"
class Class:
def __init__(self, value: int) -> None: ...
@property
def value(self) -> int: ...
def __eq__(self, value: object, /) -> bool: ...
def __ne__(self, value: object, /) -> bool: ...
def list_of_int_identity(arg: "list[int]") -> "list[int]": ...
The only piece of new syntax is that the #[pyo3(signature = ...)] attribute can contain type annotations like #[pyo3(signature = (arg: "list[int]") -> "list[int]")] (note the "" around type annotations).
This is useful when PyO3 is not able to derive proper type annotations by itself.
To generate stubs file with maturin you can use maturin generate-stubs --output stubs that will build the project then generate the stubs in the stubs directory.
You can also directly integrate the stubs in the built wheels by doing maturin build --generate-stubs (works also with maturin develop).
PyO3 also provides the smaller pyo3-introspection binary that allows to generate stubs from an existing built extension using something like pyo3-introspection my_module_binary.so my_module_name output to introspect the my_module_binary.so dynamic library for the my_module_name Python module and generate its stubs in the output directory.
Constraints and limitations
- The
experimental-inspectfeature is required to generate the introspection fragments. - Lots of features are not implemented yet. See the related issue for a list of them.
- Introspection only works with Python modules declared with an inline Rust module. Modules declared using a function are not supported.
FromPyObject::INPUT_TYPEandIntoPyObject::OUTPUT_TYPEmust be implemented for PyO3 to get the proper input/output type annotations to use.- The generated stubs depend on the Python version the extension is built for.
For example, the
disjoint_basedecorator is imported fromtypingwhen targeting Python 3.15 or newer and fromtyping_extensionsotherwise. If you commit generated stubs to your repository, generate them with the oldest Python version you support (or with the matchingabi3-pyXYfeature) so that they are valid for every version. - PyO3 is not able to introspect the content of
#[pymodule]and#[pymodule_init]functions. If they are present, the module is tagged as incomplete using a fakedef __getattr__(name: str) -> Incomplete: ...function following best practices. A#[pymodule_init]function declared without the module argument is exempt: it is not handed the module, so the module is taken to be complete. APython<'_>marker on its own does not count as the module argument.