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;
188pub mod runtime;
189mod stub_type;
190pub mod type_info;
191pub mod util;
192
193pub use generate::StubInfo;
194pub use pyproject::StubGenConfig;
195pub use stub_type::{ImportKind, ImportRef, ModuleRef, PyStubType, TypeIdentifierRef, TypeInfo};
196
197pub type Result<T> = anyhow::Result<T>;
198
199/// Create a function to initialize [StubInfo] from `pyproject.toml` in `CARGO_MANIFEST_DIR`.
200///
201/// If `pyproject.toml` is in another place, you need to create a function to call [StubInfo::from_pyproject_toml] manually.
202/// This must be placed in your PyO3 library crate, i.e. same crate where [inventory::submit]ted,
203/// not in `gen_stub` executables due to [inventory] mechanism.
204///
205#[macro_export]
206macro_rules! define_stub_info_gatherer {
207    ($function_name:ident) => {
208        /// Auto-generated function to gather information to generate stub files
209        pub fn $function_name() -> $crate::Result<$crate::StubInfo> {
210            let manifest_dir: &::std::path::Path = env!("CARGO_MANIFEST_DIR").as_ref();
211            $crate::StubInfo::from_pyproject_toml(manifest_dir.join("pyproject.toml"))
212        }
213    };
214}
215
216/// Add module-level documention using interpolation of runtime expressions.
217/// The first argument `module_doc!` receives is the full module name;
218/// the second and followings are a format string, same to `format!`.
219/// ```rust
220/// pyo3_stub_gen::module_doc!(
221///   "module.name",
222///   "Document for {} v{} ...",
223///   env!("CARGO_PKG_NAME"),
224///   env!("CARGO_PKG_VERSION")
225/// );
226/// ```
227#[macro_export]
228macro_rules! module_doc {
229    ($module:literal, $($fmt:tt)+) => {
230        $crate::inventory::submit! {
231            $crate::type_info::ModuleDocInfo {
232                module: $module,
233                doc: {
234                    fn _fmt() -> String {
235                        ::std::format!($($fmt)+)
236                    }
237                    _fmt
238                }
239            }
240        }
241    };
242}
243
244/// Add module-level variable, the first argument `module_variable!` receives is the full module name;
245/// the second argument is the name of the variable, the third argument is the type of the variable,
246/// and (optional) the fourth argument is the default value of the variable.
247/// ```rust
248/// pyo3_stub_gen::module_variable!("module.name", "CONSTANT1", usize);
249/// pyo3_stub_gen::module_variable!("module.name", "CONSTANT2", usize, 123);
250/// ```
251#[macro_export]
252macro_rules! module_variable {
253    ($module:expr, $name:expr, $ty:ty) => {
254        $crate::inventory::submit! {
255            $crate::type_info::PyVariableInfo{
256                name: $name,
257                module: $module,
258                r#type: <$ty as $crate::PyStubType>::type_output,
259                default: None,
260            }
261        }
262    };
263    ($module:expr, $name:expr, $ty:ty, $value:expr) => {
264        $crate::inventory::submit! {
265            $crate::type_info::PyVariableInfo{
266                name: $name,
267                module: $module,
268                r#type: <$ty as $crate::PyStubType>::type_output,
269                default: Some({
270                    fn _fmt() -> String {
271                        let v: $ty = $value;
272                        $crate::util::fmt_py_obj(v)
273                    }
274                    _fmt
275                }),
276            }
277        }
278    };
279}
280
281/// Define a module-level type alias with runtime support.
282///
283/// This macro creates a zero-sized struct that:
284/// - Generates a type alias entry in Python stub files (`.pyi`)
285/// - Can be registered at runtime using [`runtime::PyModuleTypeAliasExt::add_type_alias`]
286///
287/// # Syntax
288///
289/// ```rust,ignore
290/// // Union type
291/// pyo3_stub_gen::type_alias!("module.name", MyUnion = TypeA | TypeB);
292///
293/// // Single type (alias)
294/// pyo3_stub_gen::type_alias!("module.name", MyAlias = SomeType);
295///
296/// // With documentation
297/// pyo3_stub_gen::type_alias!("module.name", MyAlias = TypeA | TypeB, "Documentation string");
298/// ```
299///
300/// # Examples
301///
302/// ```rust
303/// use pyo3_stub_gen::type_alias;
304///
305/// // Define a union type alias
306/// type_alias!("my_module", NumberOrString = i32 | String);
307///
308/// // Define a single type alias
309/// type_alias!("my_module", OptionalInt = Option<i32>);
310/// ```
311///
312/// # Runtime Registration
313///
314/// To make the type alias importable from Python, register it in your module:
315///
316/// ```rust,ignore
317/// use pyo3::prelude::*;
318/// use pyo3_stub_gen::runtime::PyModuleTypeAliasExt;
319///
320/// type_alias!("my_module", NumberOrString = i32 | String);
321///
322/// #[pymodule]
323/// fn my_module(m: &Bound<PyModule>) -> PyResult<()> {
324///     m.add_type_alias::<NumberOrString>()?;
325///     Ok(())
326/// }
327/// ```
328#[macro_export]
329macro_rules! type_alias {
330    // Pattern 1: Union types with docstring - must come first
331    ($module:expr, $name:ident = $($base:ty)|+, $doc:expr) => {
332        /// Type alias generated by `type_alias!` macro.
333        pub struct $name;
334
335        impl $crate::PyStubType for $name {
336            fn type_output() -> $crate::TypeInfo {
337                $(<$base as $crate::PyStubType>::type_output()) | *
338            }
339            fn type_input() -> $crate::TypeInfo {
340                $(<$base as $crate::PyStubType>::type_input()) | *
341            }
342        }
343
344        impl $crate::runtime::PyTypeAlias for $name {
345            const NAME: &'static str = stringify!($name);
346            const MODULE: &'static str = $module;
347
348            fn create_type_object(py: ::pyo3::Python<'_>) -> ::pyo3::PyResult<::pyo3::Bound<'_, ::pyo3::PyAny>> {
349                let types: ::std::vec::Vec<::pyo3::Bound<'_, ::pyo3::PyAny>> = ::std::vec![
350                    $(<$base as $crate::runtime::PyRuntimeType>::runtime_type_object(py)?),*
351                ];
352                $crate::runtime::union_type(py, &types)
353            }
354        }
355
356        $crate::inventory::submit! {
357            $crate::type_info::TypeAliasInfo {
358                name: stringify!($name),
359                module: $module,
360                r#type: <$name as $crate::PyStubType>::type_output,
361                doc: $doc,
362            }
363        }
364    };
365
366    // Pattern 2: Union types without docstring (backward compatible)
367    ($module:expr, $name:ident = $($base:ty)|+) => {
368        $crate::type_alias!($module, $name = $($base)|+, "");
369    };
370
371    // Pattern 3: Single types with docstring
372    ($module:expr, $name:ident = $ty:ty, $doc:expr) => {
373        /// Type alias generated by `type_alias!` macro.
374        pub struct $name;
375
376        impl $crate::PyStubType for $name {
377            fn type_output() -> $crate::TypeInfo {
378                <$ty as $crate::PyStubType>::type_output()
379            }
380            fn type_input() -> $crate::TypeInfo {
381                <$ty as $crate::PyStubType>::type_input()
382            }
383        }
384
385        impl $crate::runtime::PyTypeAlias for $name {
386            const NAME: &'static str = stringify!($name);
387            const MODULE: &'static str = $module;
388
389            fn create_type_object(py: ::pyo3::Python<'_>) -> ::pyo3::PyResult<::pyo3::Bound<'_, ::pyo3::PyAny>> {
390                <$ty as $crate::runtime::PyRuntimeType>::runtime_type_object(py)
391            }
392        }
393
394        $crate::inventory::submit! {
395            $crate::type_info::TypeAliasInfo {
396                name: stringify!($name),
397                module: $module,
398                r#type: <$name as $crate::PyStubType>::type_output,
399                doc: $doc,
400            }
401        }
402    };
403
404    // Pattern 4: Single types without docstring (backward compatible)
405    ($module:expr, $name:ident = $ty:ty) => {
406        $crate::type_alias!($module, $name = $ty, "");
407    };
408}
409
410/// Re-export items from another module into __all__
411///
412/// # New syntax (recommended)
413///
414/// ## Wildcard re-export (all public items)
415/// ```rust
416/// pyo3_stub_gen::reexport_module_members!("target.module" from "source.module");
417/// ```
418///
419/// ## Wildcard with additional items (e.g., `__version__`)
420/// ```rust
421/// pyo3_stub_gen::reexport_module_members!("target.module" from "source.module"; *, "__version__");
422/// ```
423///
424/// ## Specific items only
425/// ```rust
426/// pyo3_stub_gen::reexport_module_members!("target.module" from "source.module"; "item1", "item2");
427/// ```
428///
429/// # Legacy syntax (still supported)
430///
431/// ## Wildcard re-export
432/// ```rust
433/// pyo3_stub_gen::reexport_module_members!("target.module", "source.module");
434/// ```
435///
436/// ## Specific items re-export
437/// ```rust
438/// pyo3_stub_gen::reexport_module_members!("target.module", "source.module", "item1", "item2");
439/// ```
440#[macro_export]
441macro_rules! reexport_module_members {
442    // New syntax: Wildcard - reexport_module_members!("target" from "source")
443    ($target:literal from $source:literal) => {
444        $crate::inventory::submit! {
445            $crate::type_info::ReexportModuleMembers {
446                target_module: $target,
447                source_module: $source,
448                items: $crate::type_info::ReexportItems::Wildcard,
449            }
450        }
451    };
452    // New syntax: Explicit wildcard - reexport_module_members!("target" from "source"; *)
453    ($target:literal from $source:literal; *) => {
454        $crate::inventory::submit! {
455            $crate::type_info::ReexportModuleMembers {
456                target_module: $target,
457                source_module: $source,
458                items: $crate::type_info::ReexportItems::Wildcard,
459            }
460        }
461    };
462    // New syntax: Wildcard + additional items - reexport_module_members!("target" from "source"; *, "item1", "item2")
463    ($target:literal from $source:literal; *, $($item:literal),+) => {
464        $crate::inventory::submit! {
465            $crate::type_info::ReexportModuleMembers {
466                target_module: $target,
467                source_module: $source,
468                items: $crate::type_info::ReexportItems::WildcardPlus(&[$($item),+]),
469            }
470        }
471    };
472    // New syntax: Specific items only - reexport_module_members!("target" from "source"; "item1", "item2")
473    ($target:literal from $source:literal; $($item:literal),+) => {
474        $crate::inventory::submit! {
475            $crate::type_info::ReexportModuleMembers {
476                target_module: $target,
477                source_module: $source,
478                items: $crate::type_info::ReexportItems::Explicit(&[$($item),+]),
479            }
480        }
481    };
482    // Legacy syntax: Wildcard - reexport_module_members!("target", "source")
483    ($target:expr, $source:expr) => {
484        $crate::inventory::submit! {
485            $crate::type_info::ReexportModuleMembers {
486                target_module: $target,
487                source_module: $source,
488                items: $crate::type_info::ReexportItems::Wildcard,
489            }
490        }
491    };
492    // Legacy syntax: Specific items - reexport_module_members!("target", "source", "item1", "item2")
493    ($target:expr, $source:expr, $($item:expr),+) => {
494        $crate::inventory::submit! {
495            $crate::type_info::ReexportModuleMembers {
496                target_module: $target,
497                source_module: $source,
498                items: $crate::type_info::ReexportItems::Explicit(&[$($item),+]),
499            }
500        }
501    };
502}
503
504/// Add verbatim entry to __all__
505///
506/// # Example
507/// ```rust
508/// pyo3_stub_gen::export_verbatim!("my.module", "my_name");
509/// ```
510#[macro_export]
511macro_rules! export_verbatim {
512    ($module:expr, $name:expr) => {
513        $crate::inventory::submit! {
514            $crate::type_info::ExportVerbatim {
515                target_module: $module,
516                name: $name,
517            }
518        }
519    };
520}
521
522/// Exclude specific items from __all__
523///
524/// # Example
525/// ```rust
526/// pyo3_stub_gen::exclude_from_all!("my.module", "internal_function");
527/// ```
528#[macro_export]
529macro_rules! exclude_from_all {
530    ($module:expr, $name:expr) => {
531        $crate::inventory::submit! {
532            $crate::type_info::ExcludeFromAll {
533                target_module: $module,
534                name: $name,
535            }
536        }
537    };
538}
539
540#[doc = include_str!("../README.md")]
541mod readme {}