pyo3_stub_gen/
lib.rs

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

pub use inventory;
pub use pyo3_stub_gen_derive as derive; // re-export to use in generated code

pub mod exception;
pub mod generate;
pub mod pyproject;
mod stub_type;
pub mod type_info;

pub use generate::StubInfo;
pub use stub_type::{PyStubType, TypeInfo};

pub type Result<T> = anyhow::Result<T>;

/// Create a function to initialize [StubInfo] from `pyproject.toml` in `CARGO_MANIFEST_DIR`.
///
/// If `pyproject.toml` is in another place, you need to create a function to call [StubInfo::from_pyproject_toml] manually.
/// This must be placed in your PyO3 library crate, i.e. same crate where [inventory::submit]ted,
/// not in `gen_stub` executables due to [inventory] mechanism.
///
#[macro_export]
macro_rules! define_stub_info_gatherer {
    ($function_name:ident) => {
        /// Auto-generated function to gather information to generate stub files
        pub fn $function_name() -> $crate::Result<$crate::StubInfo> {
            let manifest_dir: &::std::path::Path = env!("CARGO_MANIFEST_DIR").as_ref();
            $crate::StubInfo::from_pyproject_toml(manifest_dir.join("pyproject.toml"))
        }
    };
}

#[macro_export]
macro_rules! module_variable {
    ($module:expr, $name:expr, $ty:ty) => {
        $crate::inventory::submit! {
            $crate::type_info::PyVariableInfo{
                name: $name,
                module: $module,
                r#type: <$ty as $crate::PyStubType>::type_output,
            }
        }
    };
}