pyo3_stub_gen/
lib.rs

1//! This crate creates stub files in following three steps using [inventory] crate:
2//!
3//! Define type information in Rust code (or by proc-macro)
4//! ---------------------------------------------------------
5//! The first step is to define Python type information in Rust code. [type_info] module provides several structs, for example:
6//!
7//! - [type_info::PyFunctionInfo] stores information of Python function, i.e. the name of the function, arguments and its types, return type, etc.
8//! - [type_info::PyClassInfo] stores information for Python class definition, i.e. the name of the class, members and its types, methods, etc.
9//!
10//! For better understanding of what happens in the background, let's define these information manually:
11//!
12//! ```
13//! use pyo3::*;
14//! use pyo3_stub_gen::type_info::*;
15//!
16//! // Usual PyO3 class definition
17//! #[pyclass(module = "my_module", name = "MyClass")]
18//! struct MyClass {
19//!     #[pyo3(get)]
20//!     name: String,
21//!     #[pyo3(get)]
22//!     description: Option<String>,
23//! }
24//!
25//! // Submit type information for stub file generation to inventory
26//! inventory::submit!{
27//!     // Send information about Python class
28//!     PyClassInfo {
29//!         // Type ID of Rust struct (used to gathering phase discussed later)
30//!         struct_id: std::any::TypeId::of::<MyClass>,
31//!
32//!         // Python module name. Since stub file is generated per modules,
33//!         // this helps where the class definition should be placed.
34//!         module: Some("my_module"),
35//!
36//!         // Python class name
37//!         pyclass_name: "MyClass",
38//!
39//!         members: &[
40//!             MemberInfo {
41//!                 name: "name",
42//!                 r#type: <String as ::pyo3_stub_gen::PyStubType>::type_output,
43//!                 doc: "Name docstring",
44//!             },
45//!             MemberInfo {
46//!                 name: "description",
47//!                 r#type: <Option<String> as ::pyo3_stub_gen::PyStubType>::type_output,
48//!                 doc: "Description docstring",
49//!             },
50//!         ],
51//!
52//!         doc: "Docstring used in Python",
53//!
54//!         // Base classes
55//!         bases: &[],
56//!     }
57//! }
58//! ```
59//!
60//! Roughly speaking, the above corresponds a following stub file `my_module.pyi`:
61//!
62//! ```python
63//! class MyClass:
64//!     """
65//!     Docstring used in Python
66//!     """
67//!     name: str
68//!     """Name docstring"""
69//!     description: Optional[str]
70//!     """Description docstring"""
71//! ```
72//!
73//! We want to generate this [type_info::PyClassInfo] section automatically from `MyClass` Rust struct definition.
74//! This is done by using `#[gen_stub_pyclass]` proc-macro:
75//!
76//! ```
77//! use pyo3::*;
78//! use pyo3_stub_gen::{type_info::*, derive::gen_stub_pyclass};
79//!
80//! // Usual PyO3 class definition
81//! #[gen_stub_pyclass]
82//! #[pyclass(module = "my_module", name = "MyClass")]
83//! struct MyClass {
84//!     #[pyo3(get)]
85//!     name: String,
86//!     #[pyo3(get)]
87//!     description: Option<String>,
88//! }
89//! ```
90//!
91//! Since proc-macro is a converter from Rust code to Rust code, the output must be a Rust code.
92//! However, we need to gather these [type_info::PyClassInfo] definitions to generate stub files,
93//! and the above [inventory::submit] is for it.
94//!
95//! Gather type information into [StubInfo]
96//! ----------------------------------------
97//! [inventory] crate provides a mechanism to gather [inventory::submit]ted information when the library is loaded.
98//! To access these information through [inventory::iter], we need to define a gather function in the crate.
99//! Typically, this is done by following:
100//!
101//! ```rust
102//! use pyo3_stub_gen::{StubInfo, Result};
103//!
104//! pub fn stub_info() -> Result<StubInfo> {
105//!     let manifest_dir: &::std::path::Path = env!("CARGO_MANIFEST_DIR").as_ref();
106//!     StubInfo::from_pyproject_toml(manifest_dir.join("pyproject.toml"))
107//! }
108//! ```
109//!
110//! There is a helper macro to define it easily:
111//!
112//! ```rust
113//! pyo3_stub_gen::define_stub_info_gatherer!(sub_info);
114//! ```
115//!
116//! Generate stub file from [StubInfo]
117//! -----------------------------------
118//! [StubInfo] translates [type_info::PyClassInfo] and other information into a form helpful for generating stub files while gathering.
119//!
120//! [generate] module provides structs implementing [std::fmt::Display] to generate corresponding parts of stub file.
121//! For example, [generate::MethodDef] generates Python class method definition as follows:
122//!
123//! ```rust
124//! use pyo3_stub_gen::{TypeInfo, generate::*};
125//!
126//! let method = MethodDef {
127//!     name: "foo",
128//!     args: vec![Arg { name: "x", r#type: TypeInfo::builtin("int"), signature: None, }],
129//!     r#return: TypeInfo::builtin("int"),
130//!     doc: "This is a foo method.",
131//!     r#type: MethodType::Instance,
132//! };
133//!
134//! assert_eq!(
135//!     method.to_string().trim(),
136//!     r#"
137//!     def foo(self, x:builtins.int) -> builtins.int:
138//!         r"""
139//!         This is a foo method.
140//!         """
141//!     "#.trim()
142//! );
143//! ```
144//!
145//! [generate::ClassDef] generates Python class definition using [generate::MethodDef] and others, and other `*Def` structs works as well.
146//!
147//! [generate::Module] consists of `*Def` structs and yields an entire stub file `*.pyi` for a single Python (sub-)module, i.e. a shared library build by PyO3.
148//! [generate::Module]s are created as a part of [StubInfo], which merges [type_info::PyClassInfo]s and others submitted to [inventory] separately.
149//! [StubInfo] is instantiated with [pyproject::PyProject] to get where to generate the stub file,
150//! and [StubInfo::generate] generates the stub files for every modules.
151//!
152
153pub use inventory;
154pub use pyo3_stub_gen_derive as derive; // re-export to use in generated code
155
156pub mod exception;
157pub mod generate;
158pub mod pyproject;
159mod stub_type;
160pub mod type_info;
161pub mod util;
162
163pub use generate::StubInfo;
164pub use stub_type::{PyStubType, TypeInfo};
165
166pub type Result<T> = anyhow::Result<T>;
167
168/// Create a function to initialize [StubInfo] from `pyproject.toml` in `CARGO_MANIFEST_DIR`.
169///
170/// If `pyproject.toml` is in another place, you need to create a function to call [StubInfo::from_pyproject_toml] manually.
171/// This must be placed in your PyO3 library crate, i.e. same crate where [inventory::submit]ted,
172/// not in `gen_stub` executables due to [inventory] mechanism.
173///
174#[macro_export]
175macro_rules! define_stub_info_gatherer {
176    ($function_name:ident) => {
177        /// Auto-generated function to gather information to generate stub files
178        pub fn $function_name() -> $crate::Result<$crate::StubInfo> {
179            let manifest_dir: &::std::path::Path = env!("CARGO_MANIFEST_DIR").as_ref();
180            $crate::StubInfo::from_pyproject_toml(manifest_dir.join("pyproject.toml"))
181        }
182    };
183}
184
185#[macro_export]
186macro_rules! module_variable {
187    ($module:expr, $name:expr, $ty:ty) => {
188        $crate::inventory::submit! {
189            $crate::type_info::PyVariableInfo{
190                name: $name,
191                module: $module,
192                r#type: <$ty as $crate::PyStubType>::type_output,
193            }
194        }
195    };
196}