MCPcopy Index your code
hub / github.com/Jij-Inc/pyo3-stub-gen

github.com/Jij-Inc/pyo3-stub-gen @0.23.0

Chat with this repo
repository ↗ · DeepWiki ↗ · release 0.23.0 ↗ · + Follow
964 symbols 2,030 edges 120 files 264 documented · 27%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

pyo3-stub-gen

DeepWiki

Python stub file (*.pyi) generator for PyO3 with maturin projects.

crate name crates.io docs.rs doc (main)
pyo3-stub-gen crate docs.rs doc (main)
pyo3-stub-gen-derive crate docs.rs doc (main)

[!NOTE] Minimum supported Python version is 3.10. Do not enable 3.9 or older in PyO3 setting.

[!NOTE] Versions 0.15.0–0.17.1 unintentionally included a LGPL dependency. This was removed in 0.17.2, and the affected versions have been yanked.

Design

Our goal is to create a stub file *.pyi from Rust code, however, automated complete translation is impossible due to the difference between Rust and Python type systems and the limitation of proc-macro. We take semi-automated approach:

  • Provide a default translator which will work most cases, not all cases
  • Also provide a manual way to specify the translation.

If the default translator does not work, users can specify the translation manually, and these manual translations can be integrated with what the default translator generates. So the users can use the default translator as much as possible and only specify the translation for the edge cases.

pyo3-stub-gen crate provides the manual way to specify the translation, and pyo3-stub-gen-derive crate provides the default translator as proc-macro based on the mechanism of pyo3-stub-gen.

Usage

If you are looking for a working example, please see the examples directory.

Example Description
examples/pure Example for Pure Rust maturin project
examples/mixed Example for Mixed Rust/Python maturin project with user-written __init__.py
examples/generate_init_py Example for mixed layout with auto-generated __init__.py (Rust-only workflow)

Here we describe basic usage of pyo3-stub-gen crate based on examples/pure example.

Annotate Rust code with proc-macro

This crate provides a procedural macro #[gen_stub_pyfunction] and others to generate a Python stub file. It is used with PyO3's #[pyfunction] macro. Let's consider a simple example PyO3 project:

use pyo3::prelude::*;

#[pyfunction]
fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
    Ok((a + b).to_string())
}

#[pymodule]
fn your_module_name(m: &Bound<PyModule>) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
    Ok(())
}

To generate a stub file for this project, please modify it as follows:

use pyo3::prelude::*;
use pyo3_stub_gen::{derive::gen_stub_pyfunction, define_stub_info_gatherer};

#[gen_stub_pyfunction]  // Proc-macro attribute to register a function to stub file generator.
#[pyfunction]
fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
    Ok((a + b).to_string())
}

#[pymodule]
fn your_module_name(m: &Bound<PyModule>) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
    Ok(())
}

// Define a function to gather stub information.
define_stub_info_gatherer!(stub_info);

[!NOTE] The #[gen_stub_pyfunction] macro must be placed before #[pyfunction] macro.

#[gen_stub(skip)]

For functions or methods that you want to exclude from the generated stub file, use the #[gen_stub(skip)] attribute:

use pyo3::prelude::*;
use pyo3_stub_gen::derive::*;

#[gen_stub_pyclass]
#[pyclass]
struct MyClass;

#[gen_stub_pymethods]
#[pymethods]
impl MyClass {
    #[gen_stub(skip)]
    fn internal_method(&self) {
        // This method will not appear in the .pyi file
    }
}

#[gen_stub(default=xx)]

For getters, setters, and class attributes, you can specify default values that will appear in the stub file:

use pyo3::prelude::*;
use pyo3_stub_gen::derive::*;

#[gen_stub_pyclass]
#[pyclass]
struct Config {
    #[pyo3(get, set)]
    #[gen_stub(default = Config::default().timeout)]
    timeout: usize,
}

impl Default for Config {
    fn default() -> Self {
        Config { timeout: 30 }
    }
}

#[gen_stub_pymethods]
#[pymethods]
impl Config {
    #[getter]
    #[gen_stub(default = Config::default().timeout)]
    fn get_timeout(&self) -> usize {
        self.timeout
    }
}

Generate a stub file

And then, create an executable target in src/bin/stub_gen.rs to generate a stub file:

```rust:ignore use pyo3_stub_gen::Result;

fn main() -> Result<()> { // stub_info is a function defined by define_stub_info_gatherer! macro. let stub = pure::stub_info()?; stub.generate()?; Ok(()) }


and add `rlib` in addition to `cdylib` in `[lib]` section of `Cargo.toml`:

```toml
[lib]
crate-type = ["cdylib", "rlib"]

This target generates a stub file pure.pyi when executed.

cargo run --bin stub_gen

The stub file is automatically found by maturin, and it is included in the wheel package. See also the maturin document for more details.

Note for Mixed Layout Projects

In mixed Rust/Python projects, pyo3-stub-gen only generates stub files for PyO3-generated modules (i.e., modules at or below module-name in pyproject.toml). Stub files are not generated for pure Python parent modules to avoid shadowing user's __init__.py files.

If you are upgrading from pyo3-stub-gen v0.18.0–v0.20.0, you may have stale __init__.pyi files in pure Python directories that were previously generated. These stale files should be manually deleted, as type checkers prioritize .pyi files over .py files.

Re-exporting Module Members

In mixed layout projects, it's common to define PyO3 classes and functions in a hidden internal module (e.g., pkg._core) and re-export them to the public parent module (e.g., pkg). The reexport_module_members! macro declares this re-export relationship:

use pyo3::prelude::*;
use pyo3_stub_gen::{derive::*, reexport_module_members, define_stub_info_gatherer};

#[gen_stub_pyclass]
#[pyclass(module = "pkg._core")]
struct MyClass {
    value: i32,
}

#[gen_stub_pyfunction(module = "pkg._core")]
#[pyfunction]
fn my_function() -> i32 { 42 }

#[pymodule]
fn _core(m: &Bound<PyModule>) -> PyResult<()> {
    m.add_class::<MyClass>()?;
    m.add_function(wrap_pyfunction!(my_function, m)?)?;
    Ok(())
}

// Re-export all items from pkg._core to pkg
reexport_module_members!("pkg", "pkg._core");

define_stub_info_gatherer!(stub_info);

This macro: - Includes re-exported items in the target module's stub file (adds from source_module import ...) - Provides re-export information to documentation generation - Serves as the single source of truth for what gets re-exported

The macro can be used between PyO3-generated modules, or from a PyO3 module to a pure Python parent module:

  • PyO3 → PyO3: The stub file gets from .submod import .... You must implement the re-export in your #[pymodule] function manually.
  • PyO3 → Pure Python parent: Requires generate-init-py to be enabled (see below). Otherwise, stub_gen will fail because it cannot generate a stub for a pure Python module without also generating its __init__.py.

Auto-generating __init__.py

When your project uses mixed layout (required for generating multiple stub files) but you want a Rust-only workflow without writing Python code, the generate-init-py feature automatically generates __init__.py files with proper imports and __all__ declarations based on reexport_module_members! declarations.

Add to pyproject.toml:

[tool.maturin]
module-name = "pkg._core"  # PyO3 module is a hidden submodule
python-source = "python"

[tool.pyo3-stub-gen]
generate-init-py = true    # Enable for all packages with re-exports

Or specify which packages to generate for:

[tool.pyo3-stub-gen]
generate-init-py = ["pkg", "pkg.subpkg"]  # Enable for specific packages

Running cargo run --bin stub_gen generates python/pkg/__init__.py:

# This file is automatically generated by pyo3_stub_gen
# ruff: noqa: F401

from pkg._core import MyClass, my_function
__all__ = [
    "MyClass",
    "my_function",
]

This ensures consistency between: - Stub files: Type checkers see re-exported items in the parent module - Runtime: Python code can import from the parent module - Documentation: API docs show items under the parent module

See examples/generate_init_py for a complete working example.

Manual Overriding

When the automatic Rust-to-Python type translation doesn't produce the desired result, you can manually specify type information using Python stub syntax. There are two main approaches:

  1. Complete override - Replace entire function signature with #[gen_stub_pyfunction(python = "...")]
  2. Partial override - Override specific arguments or return types with #[gen_stub(override_type(...))]

Method 1: Complete Override Using python Parameter

Use the python parameter to specify the complete function signature in Python stub syntax. This is ideal when you need to define complex types or when the entire signature needs custom definition.

use pyo3::prelude::*;
use pyo3_stub_gen::derive::*;

#[gen_stub_pyfunction(python = r#"
    import collections.abc
    import typing

    def fn_with_callback(callback: collections.abc.Callable[[str], typing.Any]) -> collections.abc.Callable[[str], typing.Any]:
        """Example using python parameter for complete override."""
"#)]
#[pyfunction]
pub fn fn_with_callback<'a>(callback: Bound<'a, PyAny>) -> PyResult<Bound<'a, PyAny>> {
    callback.call1(("Hello!",))?;
    Ok(callback)
}

This approach: - ✅ Provides complete control over the generated stub - ✅ Supports complex types like collections.abc.Callable - ✅ Allows adding custom docstrings - ✅ Import statements are automatically extracted

Method 2: Partial Override Using Attributes

For selective overrides, use #[gen_stub(override_type(...))] on specific arguments or #[gen_stub(override_return_type(...))] on the function. This is useful when most types translate correctly but a few need adjustment.

use pyo3::prelude::*;
use pyo3_stub_gen::derive::*;

#[gen_stub_pyfunction]
#[pyfunction]
#[gen_stub(override_return_type(type_repr="collections.abc.Callable[[str], typing.Any]", imports=("collections.abc", "typing")))]
pub fn get_callback<'a>(
    #[gen_stub(override_type(type_repr="collections.abc.Callable[[str], typing.Any]", imports=("collections.abc", "typing")))]
    cb: Bound<'a, PyAny>,
) -> PyResult<Bound<'a, PyAny>> {
    Ok(cb)
}

This approach: - ✅ Fine-grained control over individual types - ✅ Preserves automatic generation for other parameters - ✅ Explicit about which types need manual specification

Method 3: Separate Definitions Using Macros

How submit! works:

The #[gen_stub_pyfunction] and #[gen_stub_pyclass] macros automatically generate submit! blocks internally to register type information. You can also manually add submit! blocks to supplement or override this automatic registration.

When multiple type signatures exist for the same function or method, the stub generator automatically generates @overload decorators in the .pyi file. This enables proper type checking for functions that accept multiple type signatures.

Two approaches for overloads:

  1. **python_overload pa

Extension points exported contracts — how you extend this code

PyStubType (Interface)
Annotate Rust types with Python type information for stub file generation. This trait is used to generate Python stub f [39 …
pyo3-stub-gen/src/stub_type.rs
PyRuntimeType (Interface)
Trait for Rust types that can be converted to Python type objects at runtime. This trait is used by [`type_alias!`](cra [34 …
pyo3-stub-gen/src/runtime/mod.rs
Import (Interface)
(no doc) [10 implementers]
pyo3-stub-gen/src/generate.rs
PyModuleTypeAliasExt (Interface)
Extension trait for `Bound ` to add type aliases. This trait provides a convenient method for registering type [1 implementers]
pyo3-stub-gen/src/runtime/mod.rs
PyTypeAlias (Interface)
Trait for type aliases that can be registered at runtime. This trait is automatically implemented by the [`type_alias!`
pyo3-stub-gen/src/runtime/mod.rs

Core symbols most depended-on inside this repo

is_empty
called by 88
pyo3-stub-gen/src/generate/module.rs
add_function
called by 85
pyo3-stub-gen/src/generate/stub_info.rs
get
called by 78
pyo3-stub-gen/src/stub_type.rs
values
called by 34
examples/pure/src/lib.rs
parse_pyo3_attrs
called by 23
pyo3-stub-gen-derive/src/gen_stub/attr.rs
any
called by 21
pyo3-stub-gen/src/stub_type.rs
parse_and_convert
called by 19
pyo3-stub-gen-derive/src/gen_stub/parse_python.rs
write_docstring
called by 17
pyo3-stub-gen/src/generate/docstring.rs

Shape

Function 449
Method 323
Class 144
Enum 42
Interface 6

Languages

Rust86%
Python14%

Modules by API surface

examples/pure/src/lib.rs72 symbols
pyo3-stub-gen/src/docgen/sphinx_ext.py55 symbols
pyo3-stub-gen-derive/src/gen_stub/parse_python.rs34 symbols
pyo3-stub-gen-derive/src/gen_stub/attr.rs32 symbols
pyo3-stub-gen/src/stub_type.rs30 symbols
pyo3-stub-gen/src/generate/stub_info.rs26 symbols
pyo3-stub-gen/src/type_info.rs25 symbols
pyo3-stub-gen/src/pyproject.rs24 symbols
examples/pure/tests/test_python.py24 symbols
pyo3-stub-gen-derive/src/gen_stub/parse_python/pyfunction.rs23 symbols
pyo3-stub-gen/src/generate/parameters.rs22 symbols
pyo3-stub-gen/src/docgen/ir.rs21 symbols

For agents

$ claude mcp add pyo3-stub-gen \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact

Ask about this repo answers extend the page