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//!         getters: &[
40//!             MemberInfo {
41//!                 name: "name",
42//!                 r#type: <String as ::pyo3_stub_gen::PyStubType>::type_output,
43//!                 doc: "Name docstring",
44//!                 default: None,
45//!                 deprecated: None,
46//!             },
47//!             MemberInfo {
48//!                 name: "description",
49//!                 r#type: <Option<String> as ::pyo3_stub_gen::PyStubType>::type_output,
50//!                 doc: "Description docstring",
51//!                 default: None,
52//!                 deprecated: None,
53//!             },
54//!         ],
55//!
56//!         setters: &[],
57//!
58//!         doc: "Docstring used in Python",
59//!
60//!         // Base classes
61//!         bases: &[],
62//!
63//!         // Decorated with `#[pyclass(eq, ord)]`
64//!         has_eq: false,
65//!         has_ord: false,
66//!         // Decorated with `#[pyclass(hash, str)]`
67//!         has_hash: false,
68//!         has_str: false,
69//!         // Decorated with `#[pyclass(subclass)]`
70//!         subclass: false,
71//!     }
72//! }
73//! ```
74//!
75//! Roughly speaking, the above corresponds a following stub file `my_module.pyi`:
76//!
77//! ```python
78//! class MyClass:
79//!     """
80//!     Docstring used in Python
81//!     """
82//!     name: str
83//!     """Name docstring"""
84//!     description: Optional[str]
85//!     """Description docstring"""
86//! ```
87//!
88//! We want to generate this [type_info::PyClassInfo] section automatically from `MyClass` Rust struct definition.
89//! This is done by using `#[gen_stub_pyclass]` proc-macro:
90//!
91//! ```
92//! use pyo3::*;
93//! use pyo3_stub_gen::{type_info::*, derive::gen_stub_pyclass};
94//!
95//! // Usual PyO3 class definition
96//! #[gen_stub_pyclass]
97//! #[pyclass(module = "my_module", name = "MyClass")]
98//! struct MyClass {
99//!     #[pyo3(get)]
100//!     name: String,
101//!     #[pyo3(get)]
102//!     description: Option<String>,
103//! }
104//! ```
105//!
106//! Since proc-macro is a converter from Rust code to Rust code, the output must be a Rust code.
107//! However, we need to gather these [type_info::PyClassInfo] definitions to generate stub files,
108//! and the above [inventory::submit] is for it.
109//!
110//! Gather type information into [StubInfo]
111//! ----------------------------------------
112//! [inventory] crate provides a mechanism to gather [inventory::submit]ted information when the library is loaded.
113//! To access these information through [inventory::iter], we need to define a gather function in the crate.
114//! Typically, this is done by following:
115//!
116//! ```rust
117//! use pyo3_stub_gen::{StubInfo, Result};
118//!
119//! pub fn stub_info() -> Result<StubInfo> {
120//!     let manifest_dir: &::std::path::Path = env!("CARGO_MANIFEST_DIR").as_ref();
121//!     StubInfo::from_pyproject_toml(manifest_dir.join("pyproject.toml"))
122//! }
123//! ```
124//!
125//! There is a helper macro to define it easily:
126//!
127//! ```rust
128//! pyo3_stub_gen::define_stub_info_gatherer!(sub_info);
129//! ```
130//!
131//! Generate stub file from [StubInfo]
132//! -----------------------------------
133//! [StubInfo] translates [type_info::PyClassInfo] and other information into a form helpful for generating stub files while gathering.
134//!
135//! [generate] module provides structs implementing [std::fmt::Display] to generate corresponding parts of stub file.
136//! For example, [generate::MethodDef] generates Python class method definition as follows:
137//!
138//! ```rust
139//! use pyo3_stub_gen::{TypeInfo, generate::*};
140//!
141//! let method = MethodDef {
142//!     name: "foo",
143//!     args: vec![Arg { name: "x", r#type: TypeInfo::builtin("int"), signature: None, }],
144//!     r#return: TypeInfo::builtin("int"),
145//!     doc: "This is a foo method.",
146//!     r#type: MethodType::Instance,
147//!     deprecated: None,
148//!     is_async: false,
149//!     type_ignored: None,
150//! };
151//!
152//! assert_eq!(
153//!     method.to_string().trim(),
154//!     r#"
155//!     def foo(self, x:builtins.int) -> builtins.int:
156//!         r"""
157//!         This is a foo method.
158//!         """
159//!     "#.trim()
160//! );
161//! ```
162//!
163//! [generate::ClassDef] generates Python class definition using [generate::MethodDef] and others, and other `*Def` structs works as well.
164//!
165//! [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.
166//! [generate::Module]s are created as a part of [StubInfo], which merges [type_info::PyClassInfo]s and others submitted to [inventory] separately.
167//! [StubInfo] is instantiated with [pyproject::PyProject] to get where to generate the stub file,
168//! and [StubInfo::generate] generates the stub files for every modules.
169//!
170
171pub use inventory;
172pub use pyo3_stub_gen_derive as derive; // re-export to use in generated code
173
174pub mod exception;
175pub mod generate;
176pub mod pyproject;
177pub mod rule_name;
178mod stub_type;
179pub mod type_info;
180pub mod util;
181
182pub use generate::StubInfo;
183pub use stub_type::{PyStubType, TypeInfo};
184
185pub type Result<T> = anyhow::Result<T>;
186
187/// Create a function to initialize [StubInfo] from `pyproject.toml` in `CARGO_MANIFEST_DIR`.
188///
189/// If `pyproject.toml` is in another place, you need to create a function to call [StubInfo::from_pyproject_toml] manually.
190/// This must be placed in your PyO3 library crate, i.e. same crate where [inventory::submit]ted,
191/// not in `gen_stub` executables due to [inventory] mechanism.
192///
193#[macro_export]
194macro_rules! define_stub_info_gatherer {
195    ($function_name:ident) => {
196        /// Auto-generated function to gather information to generate stub files
197        pub fn $function_name() -> $crate::Result<$crate::StubInfo> {
198            let manifest_dir: &::std::path::Path = env!("CARGO_MANIFEST_DIR").as_ref();
199            $crate::StubInfo::from_pyproject_toml(manifest_dir.join("pyproject.toml"))
200        }
201    };
202}
203
204/// Add module-level documention using interpolation of runtime expressions.
205/// The first argument `module_doc!` receives is the full module name;
206/// the second and followings are a format string, same to `format!`.
207/// ```rust
208/// pyo3_stub_gen::module_doc!(
209///   "module.name",
210///   "Document for {} v{} ...",
211///   env!("CARGO_PKG_NAME"),
212///   env!("CARGO_PKG_VERSION")
213/// );
214/// ```
215#[macro_export]
216macro_rules! module_doc {
217    ($module:literal, $($fmt:tt)+) => {
218        $crate::inventory::submit! {
219            $crate::type_info::ModuleDocInfo {
220                module: $module,
221                doc: {
222                    fn _fmt() -> String {
223                        ::std::format!($($fmt)+)
224                    }
225                    _fmt
226                }
227            }
228        }
229    };
230}
231
232/// Add module-level variable, the first argument `module_variable!` receives is the full module name;
233/// the second argument is the name of the variable, the third argument is the type of the variable,
234/// and (optional) the fourth argument is the default value of the variable.
235/// ```rust
236/// pyo3_stub_gen::module_variable!("module.name", "CONSTANT1", usize);
237/// pyo3_stub_gen::module_variable!("module.name", "CONSTANT2", usize, 123);
238/// ```
239#[macro_export]
240macro_rules! module_variable {
241    ($module:expr, $name:expr, $ty:ty) => {
242        $crate::inventory::submit! {
243            $crate::type_info::PyVariableInfo{
244                name: $name,
245                module: $module,
246                r#type: <$ty as $crate::PyStubType>::type_output,
247                default: None,
248            }
249        }
250    };
251    ($module:expr, $name:expr, $ty:ty, $value:expr) => {
252        $crate::inventory::submit! {
253            $crate::type_info::PyVariableInfo{
254                name: $name,
255                module: $module,
256                r#type: <$ty as $crate::PyStubType>::type_output,
257                default: Some({
258                    fn _fmt() -> String {
259                        let v: $ty = $value;
260                        $crate::util::fmt_py_obj(v)
261                    }
262                    _fmt
263                }),
264            }
265        }
266    };
267}
268
269#[doc = include_str!("../README.md")]
270mod readme {}