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::*, type_info::ParameterKind};
140//!
141//! let method = MethodDef {
142//! name: "foo",
143//! parameters: Parameters {
144//! positional_or_keyword: vec![Parameter {
145//! name: "x",
146//! kind: ParameterKind::PositionalOrKeyword,
147//! type_info: TypeInfo::builtin("int"),
148//! default: ParameterDefault::None,
149//! }],
150//! ..Parameters::new()
151//! },
152//! r#return: TypeInfo::builtin("int"),
153//! doc: "This is a foo method.",
154//! r#type: MethodType::Instance,
155//! deprecated: None,
156//! is_async: false,
157//! type_ignored: None,
158//! is_overload: false,
159//! };
160//!
161//! assert_eq!(
162//! method.to_string().trim(),
163//! r#"
164//! def foo(self, x: builtins.int) -> builtins.int:
165//! r"""
166//! This is a foo method.
167//! """
168//! "#.trim()
169//! );
170//! ```
171//!
172//! [generate::ClassDef] generates Python class definition using [generate::MethodDef] and others, and other `*Def` structs works as well.
173//!
174//! [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.
175//! [generate::Module]s are created as a part of [StubInfo], which merges [type_info::PyClassInfo]s and others submitted to [inventory] separately.
176//! [StubInfo] is instantiated with [pyproject::PyProject] to get where to generate the stub file,
177//! and [StubInfo::generate] generates the stub files for every modules.
178//!
179
180pub use inventory;
181pub use pyo3_stub_gen_derive as derive; // re-export to use in generated code
182
183pub mod docgen;
184pub mod exception;
185pub mod generate;
186pub mod pyproject;
187pub mod rule_name;
188mod stub_type;
189pub mod type_info;
190pub mod util;
191
192pub use generate::StubInfo;
193pub use pyproject::StubGenConfig;
194pub use stub_type::{ImportKind, ImportRef, ModuleRef, PyStubType, TypeIdentifierRef, TypeInfo};
195
196pub type Result<T> = anyhow::Result<T>;
197
198/// Create a function to initialize [StubInfo] from `pyproject.toml` in `CARGO_MANIFEST_DIR`.
199///
200/// If `pyproject.toml` is in another place, you need to create a function to call [StubInfo::from_pyproject_toml] manually.
201/// This must be placed in your PyO3 library crate, i.e. same crate where [inventory::submit]ted,
202/// not in `gen_stub` executables due to [inventory] mechanism.
203///
204#[macro_export]
205macro_rules! define_stub_info_gatherer {
206 ($function_name:ident) => {
207 /// Auto-generated function to gather information to generate stub files
208 pub fn $function_name() -> $crate::Result<$crate::StubInfo> {
209 let manifest_dir: &::std::path::Path = env!("CARGO_MANIFEST_DIR").as_ref();
210 $crate::StubInfo::from_pyproject_toml(manifest_dir.join("pyproject.toml"))
211 }
212 };
213}
214
215/// Add module-level documention using interpolation of runtime expressions.
216/// The first argument `module_doc!` receives is the full module name;
217/// the second and followings are a format string, same to `format!`.
218/// ```rust
219/// pyo3_stub_gen::module_doc!(
220/// "module.name",
221/// "Document for {} v{} ...",
222/// env!("CARGO_PKG_NAME"),
223/// env!("CARGO_PKG_VERSION")
224/// );
225/// ```
226#[macro_export]
227macro_rules! module_doc {
228 ($module:literal, $($fmt:tt)+) => {
229 $crate::inventory::submit! {
230 $crate::type_info::ModuleDocInfo {
231 module: $module,
232 doc: {
233 fn _fmt() -> String {
234 ::std::format!($($fmt)+)
235 }
236 _fmt
237 }
238 }
239 }
240 };
241}
242
243/// Add module-level variable, the first argument `module_variable!` receives is the full module name;
244/// the second argument is the name of the variable, the third argument is the type of the variable,
245/// and (optional) the fourth argument is the default value of the variable.
246/// ```rust
247/// pyo3_stub_gen::module_variable!("module.name", "CONSTANT1", usize);
248/// pyo3_stub_gen::module_variable!("module.name", "CONSTANT2", usize, 123);
249/// ```
250#[macro_export]
251macro_rules! module_variable {
252 ($module:expr, $name:expr, $ty:ty) => {
253 $crate::inventory::submit! {
254 $crate::type_info::PyVariableInfo{
255 name: $name,
256 module: $module,
257 r#type: <$ty as $crate::PyStubType>::type_output,
258 default: None,
259 }
260 }
261 };
262 ($module:expr, $name:expr, $ty:ty, $value:expr) => {
263 $crate::inventory::submit! {
264 $crate::type_info::PyVariableInfo{
265 name: $name,
266 module: $module,
267 r#type: <$ty as $crate::PyStubType>::type_output,
268 default: Some({
269 fn _fmt() -> String {
270 let v: $ty = $value;
271 $crate::util::fmt_py_obj(v)
272 }
273 _fmt
274 }),
275 }
276 }
277 };
278}
279
280/// Add module-level type alias using TypeInfo
281///
282/// This macro supports both single types and union types.
283///
284/// # Examples
285///
286/// Single type:
287/// ```rust
288/// pyo3_stub_gen::type_alias!("module.name", MyAlias = Option<usize>);
289/// ```
290///
291/// Union type (direct syntax):
292/// ```rust
293/// pyo3_stub_gen::type_alias!("module.name", MyUnion = i32 | String);
294/// ```
295/// ```rust,ignore
296/// pyo3_stub_gen::type_alias!("module.name", StructUnion = Bound<'static, TypeA> | Bound<'static, TypeB>);
297/// ```
298#[macro_export]
299macro_rules! type_alias {
300 // Pattern 1: Union types with docstring - must come first
301 ($module:expr, $name:ident = $($base:ty)|+, $doc:expr) => {
302 const _: () = {
303 struct __TypeAliasImpl;
304
305 impl $crate::PyStubType for __TypeAliasImpl {
306 fn type_output() -> $crate::TypeInfo {
307 $(<$base>::type_output()) | *
308 }
309 fn type_input() -> $crate::TypeInfo {
310 $(<$base>::type_input()) | *
311 }
312 }
313
314 $crate::inventory::submit! {
315 $crate::type_info::TypeAliasInfo {
316 name: stringify!($name),
317 module: $module,
318 r#type: <__TypeAliasImpl as $crate::PyStubType>::type_output,
319 doc: $doc,
320 }
321 }
322 };
323 };
324
325 // Pattern 2: Union types without docstring (backward compatible)
326 ($module:expr, $name:ident = $($base:ty)|+) => {
327 const _: () = {
328 struct __TypeAliasImpl;
329
330 impl $crate::PyStubType for __TypeAliasImpl {
331 fn type_output() -> $crate::TypeInfo {
332 $(<$base>::type_output()) | *
333 }
334 fn type_input() -> $crate::TypeInfo {
335 $(<$base>::type_input()) | *
336 }
337 }
338
339 $crate::inventory::submit! {
340 $crate::type_info::TypeAliasInfo {
341 name: stringify!($name),
342 module: $module,
343 r#type: <__TypeAliasImpl as $crate::PyStubType>::type_output,
344 doc: "",
345 }
346 }
347 };
348 };
349
350 // Pattern 3: Single types with docstring
351 ($module:expr, $name:ident = $ty:ty, $doc:expr) => {
352 $crate::inventory::submit! {
353 $crate::type_info::TypeAliasInfo {
354 name: stringify!($name),
355 module: $module,
356 r#type: <$ty as $crate::PyStubType>::type_output,
357 doc: $doc,
358 }
359 }
360 };
361
362 // Pattern 4: Single types without docstring (backward compatible)
363 ($module:expr, $name:ident = $ty:ty) => {
364 $crate::inventory::submit! {
365 $crate::type_info::TypeAliasInfo {
366 name: stringify!($name),
367 module: $module,
368 r#type: <$ty as $crate::PyStubType>::type_output,
369 doc: "",
370 }
371 }
372 };
373}
374
375/// Re-export items from another module into __all__
376///
377/// # Wildcard re-export
378/// ```rust
379/// pyo3_stub_gen::reexport_module_members!("target.module", "source.module");
380/// ```
381///
382/// # Specific items re-export
383/// ```rust
384/// pyo3_stub_gen::reexport_module_members!("target.module", "source.module", "item1", "item2");
385/// ```
386#[macro_export]
387macro_rules! reexport_module_members {
388 // Wildcard: reexport_module_members!("target", "source")
389 ($target:expr, $source:expr) => {
390 $crate::inventory::submit! {
391 $crate::type_info::ReexportModuleMembers {
392 target_module: $target,
393 source_module: $source,
394 items: None,
395 }
396 }
397 };
398 // Specific items: reexport_module_members!("target", "source", "item1", "item2")
399 ($target:expr, $source:expr, $($item:expr),+) => {
400 $crate::inventory::submit! {
401 $crate::type_info::ReexportModuleMembers {
402 target_module: $target,
403 source_module: $source,
404 items: Some(&[$($item),+]),
405 }
406 }
407 };
408}
409
410/// Add verbatim entry to __all__
411///
412/// # Example
413/// ```rust
414/// pyo3_stub_gen::export_verbatim!("my.module", "my_name");
415/// ```
416#[macro_export]
417macro_rules! export_verbatim {
418 ($module:expr, $name:expr) => {
419 $crate::inventory::submit! {
420 $crate::type_info::ExportVerbatim {
421 target_module: $module,
422 name: $name,
423 }
424 }
425 };
426}
427
428/// Exclude specific items from __all__
429///
430/// # Example
431/// ```rust
432/// pyo3_stub_gen::exclude_from_all!("my.module", "internal_function");
433/// ```
434#[macro_export]
435macro_rules! exclude_from_all {
436 ($module:expr, $name:expr) => {
437 $crate::inventory::submit! {
438 $crate::type_info::ExcludeFromAll {
439 target_module: $module,
440 name: $name,
441 }
442 }
443 };
444}
445
446#[doc = include_str!("../README.md")]
447mod readme {}