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,
}
}
};
}