From d1dfcde84e6a0be1251fc67ce615626786213b9d Mon Sep 17 00:00:00 2001 From: Yannick Molinghen Date: Tue, 24 Sep 2024 13:56:16 +0200 Subject: [PATCH 01/10] Enums with methods --- pyo3-stub-gen-derive/src/gen_stub/member.rs | 16 ++++++++++++++-- pyo3-stub-gen/src/generate/enum_.rs | 12 ++++++++++++ pyo3-stub-gen/src/generate/member.rs | 19 ++++++++++++++++++- pyo3-stub-gen/src/generate/stub_info.rs | 17 ++++++++++++++++- pyo3-stub-gen/src/type_info.rs | 1 + 5 files changed, 61 insertions(+), 4 deletions(-) diff --git a/pyo3-stub-gen-derive/src/gen_stub/member.rs b/pyo3-stub-gen-derive/src/gen_stub/member.rs index 6517297..e962c38 100644 --- a/pyo3-stub-gen-derive/src/gen_stub/member.rs +++ b/pyo3-stub-gen-derive/src/gen_stub/member.rs @@ -1,3 +1,5 @@ +use crate::gen_stub::extract_documents; + use super::{escape_return_type, parse_pyo3_attrs, Attr}; use proc_macro2::TokenStream as TokenStream2; @@ -6,6 +8,7 @@ use syn::{Error, Field, ImplItemFn, Result, Type}; #[derive(Debug)] pub struct MemberInfo { + doc: String, name: String, r#type: Type, } @@ -29,10 +32,12 @@ impl TryFrom for MemberInfo { fn try_from(item: ImplItemFn) -> Result { assert!(Self::is_candidate_item(&item)?); let ImplItemFn { attrs, sig, .. } = &item; + let doc = extract_documents(&attrs).join("\n"); let attrs = parse_pyo3_attrs(attrs)?; for attr in attrs { if let Attr::Getter(name) = attr { return Ok(MemberInfo { + doc, name: name.unwrap_or(sig.ident.to_string()), r#type: escape_return_type(&sig.output).expect("Getter must return a type"), }); @@ -54,21 +59,28 @@ impl TryFrom for MemberInfo { field_name = Some(name); } } + let doc = extract_documents(&attrs).join("\n"); Ok(Self { name: field_name.unwrap_or(ident.unwrap().to_string()), r#type: ty, + doc: doc, }) } } impl ToTokens for MemberInfo { fn to_tokens(&self, tokens: &mut TokenStream2) { - let Self { name, r#type: ty } = self; + let Self { + name, + r#type: ty, + doc, + } = self; let name = name.strip_prefix("get_").unwrap_or(name); tokens.append_all(quote! { ::pyo3_stub_gen::type_info::MemberInfo { name: #name, - r#type: <#ty as ::pyo3_stub_gen::PyStubType>::type_output + r#type: <#ty as ::pyo3_stub_gen::PyStubType>::type_output, + doc: #doc, } }) } diff --git a/pyo3-stub-gen/src/generate/enum_.rs b/pyo3-stub-gen/src/generate/enum_.rs index b268bdf..065eb60 100644 --- a/pyo3-stub-gen/src/generate/enum_.rs +++ b/pyo3-stub-gen/src/generate/enum_.rs @@ -7,6 +7,8 @@ pub struct EnumDef { pub name: &'static str, pub doc: &'static str, pub variants: &'static [&'static str], + pub methods: Vec, + pub members: Vec, } impl From<&PyEnumInfo> for EnumDef { @@ -15,6 +17,8 @@ impl From<&PyEnumInfo> for EnumDef { name: info.pyclass_name, doc: info.doc, variants: info.variants, + methods: Vec::new(), + members: Vec::new(), } } } @@ -35,6 +39,14 @@ impl fmt::Display for EnumDef { writeln!(f, "{indent}{} = auto()", variants)?; } writeln!(f)?; + for member in &self.members { + member.fmt(f)?; + } + writeln!(f)?; + for methods in &self.methods { + methods.fmt(f)?; + } + writeln!(f)?; Ok(()) } } diff --git a/pyo3-stub-gen/src/generate/member.rs b/pyo3-stub-gen/src/generate/member.rs index 66085f6..0749064 100644 --- a/pyo3-stub-gen/src/generate/member.rs +++ b/pyo3-stub-gen/src/generate/member.rs @@ -4,8 +4,10 @@ use std::{collections::HashSet, fmt}; /// Definition of a class member. #[derive(Debug, Clone, PartialEq)] pub struct MemberDef { + pub is_property: bool, pub name: &'static str, pub r#type: TypeInfo, + pub doc: &'static str, } impl Import for MemberDef { @@ -17,8 +19,10 @@ impl Import for MemberDef { impl From<&MemberInfo> for MemberDef { fn from(info: &MemberInfo) -> Self { Self { + is_property: false, name: info.name, r#type: (info.r#type)(), + doc: info.doc, } } } @@ -26,6 +30,19 @@ impl From<&MemberInfo> for MemberDef { impl fmt::Display for MemberDef { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let indent = indent(); - writeln!(f, "{indent}{}: {}", self.name, self.r#type) + if self.is_property { + writeln!(f, "{indent}@property")?; + writeln!(f, "{indent}def {}(self) -> {}:", self.name, self.r#type)?; + if !self.doc.is_empty() { + writeln!(f, r#"{indent} """{}""""#, self.doc)?; + } + writeln!(f, "{indent} ...") + } else { + writeln!(f, "{indent}{}: {}", self.name, self.r#type)?; + if !self.doc.is_empty() { + writeln!(f, r#"{indent}"""{}""""#, self.doc)?; + } + Ok(()) + } } } diff --git a/pyo3-stub-gen/src/generate/stub_info.rs b/pyo3-stub-gen/src/generate/stub_info.rs index b026f87..d29ff10 100644 --- a/pyo3-stub-gen/src/generate/stub_info.rs +++ b/pyo3-stub-gen/src/generate/stub_info.rs @@ -120,8 +120,10 @@ impl StubInfoBuilder { if let Some(entry) = module.class.get_mut(&struct_id) { for getter in info.getters { entry.members.push(MemberDef { + is_property: false, name: getter.name, r#type: (getter.r#type)(), + doc: getter.doc, }); } for method in info.methods { @@ -131,9 +133,22 @@ impl StubInfoBuilder { entry.new = Some(NewDef::from(new)); } return; + } else if let Some(entry) = module.enum_.get_mut(&struct_id) { + for getter in info.getters { + entry.members.push(MemberDef { + is_property: true, + name: getter.name, + r#type: (getter.r#type)(), + doc: getter.doc, + }); + } + for method in info.methods { + entry.methods.push(MethodDef::from(method)) + } + return; } } - unreachable!("Missing struct_id = {:?}", struct_id); + unreachable!("Missing struct_id/enum_id = {:?}", struct_id); } fn build(mut self) -> StubInfo { diff --git a/pyo3-stub-gen/src/type_info.rs b/pyo3-stub-gen/src/type_info.rs index 1ae983a..22a792a 100644 --- a/pyo3-stub-gen/src/type_info.rs +++ b/pyo3-stub-gen/src/type_info.rs @@ -58,6 +58,7 @@ pub struct MethodInfo { pub struct MemberInfo { pub name: &'static str, pub r#type: fn() -> TypeInfo, + pub doc: &'static str, } /// Info of `#[new]`-attributed methods appears in `#[pymethods]` From 5fd712bcace754e75caceacfb3eecfda5c6c18dd Mon Sep 17 00:00:00 2001 From: Yannick Molinghen Date: Tue, 24 Sep 2024 14:42:42 +0200 Subject: [PATCH 02/10] Better member documentation indent --- pyo3-stub-gen/src/generate/member.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/pyo3-stub-gen/src/generate/member.rs b/pyo3-stub-gen/src/generate/member.rs index 0749064..00964d1 100644 --- a/pyo3-stub-gen/src/generate/member.rs +++ b/pyo3-stub-gen/src/generate/member.rs @@ -1,5 +1,10 @@ +use itertools::Itertools; + use crate::{generate::*, type_info::*, TypeInfo}; -use std::{collections::HashSet, fmt}; +use std::{ + collections::HashSet, + fmt::{self, format}, +}; /// Definition of a class member. #[derive(Debug, Clone, PartialEq)] @@ -30,17 +35,18 @@ impl From<&MemberInfo> for MemberDef { impl fmt::Display for MemberDef { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let indent = indent(); + let doc = self.doc.split("\n").join(&format!("\n{indent}")); if self.is_property { writeln!(f, "{indent}@property")?; writeln!(f, "{indent}def {}(self) -> {}:", self.name, self.r#type)?; - if !self.doc.is_empty() { - writeln!(f, r#"{indent} """{}""""#, self.doc)?; + if !doc.is_empty() { + writeln!(f, r#"{indent} """{doc}""""#)?; } writeln!(f, "{indent} ...") } else { writeln!(f, "{indent}{}: {}", self.name, self.r#type)?; - if !self.doc.is_empty() { - writeln!(f, r#"{indent}"""{}""""#, self.doc)?; + if !doc.is_empty() { + writeln!(f, r#"{indent}"""{doc}""""#)?; } Ok(()) } From 4fda60a374fc4499323d01004d3bc7da690ebe07 Mon Sep 17 00:00:00 2001 From: Yannick Molinghen Date: Wed, 19 Feb 2025 14:59:07 +0100 Subject: [PATCH 03/10] Added `enum` method and property generation to "mixed" example --- examples/pure/pure.pyi | 48 ++++++++------------ examples/pure/src/lib.rs | 16 +++++++ pyo3-stub-gen-derive/src/gen_stub.rs | 3 ++ pyo3-stub-gen-derive/src/gen_stub/pyclass.rs | 3 ++ pyo3-stub-gen/src/generate/enum_.rs | 4 +- pyo3-stub-gen/src/generate/member.rs | 2 +- pyo3-stub-gen/src/lib.rs | 4 ++ 7 files changed, 49 insertions(+), 31 deletions(-) diff --git a/examples/pure/pure.pyi b/examples/pure/pure.pyi index e75abab..ad44592 100644 --- a/examples/pure/pure.pyi +++ b/examples/pure/pure.pyi @@ -8,49 +8,41 @@ import typing from enum import Enum, auto MY_CONSTANT: builtins.int + class A: x: builtins.int - def __new__(cls,x:builtins.int): ... - def show_x(self) -> None: - ... - - def ref_test(self, x:dict) -> dict: - ... - + def __new__(cls, x: builtins.int): ... + def show_x(self) -> None: ... + def ref_test(self, x: dict) -> dict: ... class Number(Enum): FLOAT = auto() INTEGER = auto() + @property + def is_float(self) -> builtins.bool: + """Whether the number is a float.""" + ... + @property + def is_integer(self) -> builtins.bool: + """Whether the number is an integer.""" + ... -def ahash_dict() -> builtins.dict[builtins.str, builtins.int]: - ... - -def create_a(x:builtins.int=2) -> A: - ... - -def create_dict(n:builtins.int) -> builtins.dict[builtins.int, builtins.list[builtins.int]]: - ... - -def default_value(num:Number=...) -> Number: - ... - -def echo_path(path:builtins.str | os.PathLike | pathlib.Path) -> builtins.str: - ... - -def read_dict(dict:typing.Mapping[builtins.int, typing.Mapping[builtins.int, builtins.int]]) -> None: - ... - -def str_len(x:builtins.str) -> builtins.int: +def ahash_dict() -> builtins.dict[builtins.str, builtins.int]: ... +def create_a(x: builtins.int = 2) -> A: ... +def create_dict(n: builtins.int) -> builtins.dict[builtins.int, builtins.list[builtins.int]]: ... +def default_value(num: Number = ...) -> Number: ... +def echo_path(path: builtins.str | os.PathLike | pathlib.Path) -> builtins.str: ... +def read_dict(dict: typing.Mapping[builtins.int, typing.Mapping[builtins.int, builtins.int]]) -> None: ... +def str_len(x: builtins.str) -> builtins.int: r""" Returns the length of the string. """ ... -def sum(v:typing.Sequence[builtins.int]) -> builtins.int: +def sum(v: typing.Sequence[builtins.int]) -> builtins.int: r""" Returns the sum of two numbers as a string. """ ... class MyError(RuntimeError): ... - diff --git a/examples/pure/src/lib.rs b/examples/pure/src/lib.rs index 91f60c2..1454844 100644 --- a/examples/pure/src/lib.rs +++ b/examples/pure/src/lib.rs @@ -100,6 +100,22 @@ pub enum Number { Integer, } +#[gen_stub_pymethods] +#[pymethods] +impl Number { + #[getter] + /// Whether the number is a float. + fn is_float(&self) -> bool { + matches!(self, Self::Float) + } + + #[getter] + /// Whether the number is an integer. + fn is_integer(&self) -> bool { + matches!(self, Self::Integer) + } +} + module_variable!("pure", "MY_CONSTANT", usize); // Test if non-any PyObject Target can be a default value diff --git a/pyo3-stub-gen-derive/src/gen_stub.rs b/pyo3-stub-gen-derive/src/gen_stub.rs index 4e6436f..ba4a5f2 100644 --- a/pyo3-stub-gen-derive/src/gen_stub.rs +++ b/pyo3-stub-gen-derive/src/gen_stub.rs @@ -15,14 +15,17 @@ //! MemberInfo { //! name: "name", //! r#type: ::type_output, +//! doc: "", //! }, //! MemberInfo { //! name: "ndim", //! r#type: ::type_output, +//! doc: "", //! }, //! MemberInfo { //! name: "description", //! r#type: as ::pyo3_stub_gen::PyStubType>::type_output, +//! doc: "", //! }, //! ], //! doc: "", diff --git a/pyo3-stub-gen-derive/src/gen_stub/pyclass.rs b/pyo3-stub-gen-derive/src/gen_stub/pyclass.rs index d2945c0..8df7d41 100644 --- a/pyo3-stub-gen-derive/src/gen_stub/pyclass.rs +++ b/pyo3-stub-gen-derive/src/gen_stub/pyclass.rs @@ -124,14 +124,17 @@ mod test { ::pyo3_stub_gen::type_info::MemberInfo { name: "name", r#type: ::type_output, + doc: "", }, ::pyo3_stub_gen::type_info::MemberInfo { name: "ndim", r#type: ::type_output, + doc: "", }, ::pyo3_stub_gen::type_info::MemberInfo { name: "description", r#type: as ::pyo3_stub_gen::PyStubType>::type_output, + doc: "", }, ], module: Some("my_module"), diff --git a/pyo3-stub-gen/src/generate/enum_.rs b/pyo3-stub-gen/src/generate/enum_.rs index 065eb60..15b2ccc 100644 --- a/pyo3-stub-gen/src/generate/enum_.rs +++ b/pyo3-stub-gen/src/generate/enum_.rs @@ -38,12 +38,12 @@ impl fmt::Display for EnumDef { for variants in self.variants { writeln!(f, "{indent}{} = auto()", variants)?; } - writeln!(f)?; for member in &self.members { + writeln!(f)?; member.fmt(f)?; } - writeln!(f)?; for methods in &self.methods { + writeln!(f)?; methods.fmt(f)?; } writeln!(f)?; diff --git a/pyo3-stub-gen/src/generate/member.rs b/pyo3-stub-gen/src/generate/member.rs index 00964d1..1f3fca6 100644 --- a/pyo3-stub-gen/src/generate/member.rs +++ b/pyo3-stub-gen/src/generate/member.rs @@ -3,7 +3,7 @@ use itertools::Itertools; use crate::{generate::*, type_info::*, TypeInfo}; use std::{ collections::HashSet, - fmt::{self, format}, + fmt::{self}, }; /// Definition of a class member. diff --git a/pyo3-stub-gen/src/lib.rs b/pyo3-stub-gen/src/lib.rs index 2b73661..1116453 100644 --- a/pyo3-stub-gen/src/lib.rs +++ b/pyo3-stub-gen/src/lib.rs @@ -40,10 +40,12 @@ //! MemberInfo { //! name: "name", //! r#type: ::type_output, +//! doc: "Name docstring", //! }, //! MemberInfo { //! name: "description", //! r#type: as ::pyo3_stub_gen::PyStubType>::type_output, +//! doc: "Description docstring", //! }, //! ], //! doc: "Docstring used in Python", @@ -59,7 +61,9 @@ //! Docstring used in Python //! """ //! name: str +//! """Name docstring""" //! description: Optional[str] +//! """Description docstring""" //! ``` //! //! We want to generate this [type_info::PyClassInfo] section automatically from `MyClass` Rust struct definition. From af89ec899712559cf7965491d5620f5e55162ed2 Mon Sep 17 00:00:00 2001 From: Yannick Molinghen Date: Wed, 19 Feb 2025 15:28:19 +0100 Subject: [PATCH 04/10] Clippy & python formatting --- examples/mixed/python/mixed/main_mod.pyi | 16 ++++------------ .../python/mixed_sub/main_mod/__init__.pyi | 16 ++++------------ .../mixed_sub/python/mixed_sub/main_mod/int.pyi | 4 +--- .../python/mixed_sub/main_mod/sub_mod.pyi | 8 ++------ examples/pure/pure.pyi | 4 ++-- pyo3-stub-gen-derive/src/gen_stub/member.rs | 4 ++-- pyo3-stub-gen/src/generate/function.rs | 5 +++-- pyo3-stub-gen/src/generate/method.rs | 9 +++++---- 8 files changed, 23 insertions(+), 43 deletions(-) diff --git a/examples/mixed/python/mixed/main_mod.pyi b/examples/mixed/python/mixed/main_mod.pyi index 3c9eb5e..2e62eb6 100644 --- a/examples/mixed/python/mixed/main_mod.pyi +++ b/examples/mixed/python/mixed/main_mod.pyi @@ -4,18 +4,10 @@ import builtins class A: - def show_x(self) -> None: - ... - + def show_x(self) -> None: ... class B: - def show_x(self) -> None: - ... - - -def create_a(x:builtins.int) -> A: - ... - -def create_b(x:builtins.int) -> B: - ... + def show_x(self) -> None: ... +def create_a(x: builtins.int) -> A: ... +def create_b(x: builtins.int) -> B: ... diff --git a/examples/mixed_sub/python/mixed_sub/main_mod/__init__.pyi b/examples/mixed_sub/python/mixed_sub/main_mod/__init__.pyi index 141b654..c051c6d 100644 --- a/examples/mixed_sub/python/mixed_sub/main_mod/__init__.pyi +++ b/examples/mixed_sub/python/mixed_sub/main_mod/__init__.pyi @@ -6,18 +6,10 @@ from . import int from . import sub_mod class A: - def show_x(self) -> None: - ... - + def show_x(self) -> None: ... class B: - def show_x(self) -> None: - ... - - -def create_a(x:builtins.int) -> A: - ... - -def create_b(x:builtins.int) -> B: - ... + def show_x(self) -> None: ... +def create_a(x: builtins.int) -> A: ... +def create_b(x: builtins.int) -> B: ... diff --git a/examples/mixed_sub/python/mixed_sub/main_mod/int.pyi b/examples/mixed_sub/python/mixed_sub/main_mod/int.pyi index e00ed50..f36463b 100644 --- a/examples/mixed_sub/python/mixed_sub/main_mod/int.pyi +++ b/examples/mixed_sub/python/mixed_sub/main_mod/int.pyi @@ -3,6 +3,4 @@ import builtins -def dummy_int_fun(x:builtins.int) -> builtins.int: - ... - +def dummy_int_fun(x: builtins.int) -> builtins.int: ... diff --git a/examples/mixed_sub/python/mixed_sub/main_mod/sub_mod.pyi b/examples/mixed_sub/python/mixed_sub/main_mod/sub_mod.pyi index 9be58b3..c78bc77 100644 --- a/examples/mixed_sub/python/mixed_sub/main_mod/sub_mod.pyi +++ b/examples/mixed_sub/python/mixed_sub/main_mod/sub_mod.pyi @@ -4,10 +4,6 @@ import builtins class C: - def show_x(self) -> None: - ... - - -def create_c(x:builtins.int) -> C: - ... + def show_x(self) -> None: ... +def create_c(x: builtins.int) -> C: ... diff --git a/examples/pure/pure.pyi b/examples/pure/pure.pyi index ad44592..d588a43 100644 --- a/examples/pure/pure.pyi +++ b/examples/pure/pure.pyi @@ -18,10 +18,12 @@ class A: class Number(Enum): FLOAT = auto() INTEGER = auto() + @property def is_float(self) -> builtins.bool: """Whether the number is a float.""" ... + @property def is_integer(self) -> builtins.bool: """Whether the number is an integer.""" @@ -37,12 +39,10 @@ def str_len(x: builtins.str) -> builtins.int: r""" Returns the length of the string. """ - ... def sum(v: typing.Sequence[builtins.int]) -> builtins.int: r""" Returns the sum of two numbers as a string. """ - ... class MyError(RuntimeError): ... diff --git a/pyo3-stub-gen-derive/src/gen_stub/member.rs b/pyo3-stub-gen-derive/src/gen_stub/member.rs index e962c38..4036b85 100644 --- a/pyo3-stub-gen-derive/src/gen_stub/member.rs +++ b/pyo3-stub-gen-derive/src/gen_stub/member.rs @@ -32,7 +32,7 @@ impl TryFrom for MemberInfo { fn try_from(item: ImplItemFn) -> Result { assert!(Self::is_candidate_item(&item)?); let ImplItemFn { attrs, sig, .. } = &item; - let doc = extract_documents(&attrs).join("\n"); + let doc = extract_documents(attrs).join("\n"); let attrs = parse_pyo3_attrs(attrs)?; for attr in attrs { if let Attr::Getter(name) = attr { @@ -63,7 +63,7 @@ impl TryFrom for MemberInfo { Ok(Self { name: field_name.unwrap_or(ident.unwrap().to_string()), r#type: ty, - doc: doc, + doc, }) } } diff --git a/pyo3-stub-gen/src/generate/function.rs b/pyo3-stub-gen/src/generate/function.rs index 8af607c..46a03cd 100644 --- a/pyo3-stub-gen/src/generate/function.rs +++ b/pyo3-stub-gen/src/generate/function.rs @@ -40,7 +40,7 @@ impl fmt::Display for FunctionDef { write!(f, ", ")?; } } - writeln!(f, ") -> {}:", self.r#return)?; + write!(f, ") -> {}:", self.r#return)?; let doc = self.doc; let indent = indent(); @@ -50,8 +50,9 @@ impl fmt::Display for FunctionDef { writeln!(f, "{indent}{}", line)?; } writeln!(f, r#"{indent}""""#)?; + } else { + writeln!(f, " ...")?; } - writeln!(f, "{indent}...")?; writeln!(f)?; Ok(()) } diff --git a/pyo3-stub-gen/src/generate/method.rs b/pyo3-stub-gen/src/generate/method.rs index d2b4754..7d30ffb 100644 --- a/pyo3-stub-gen/src/generate/method.rs +++ b/pyo3-stub-gen/src/generate/method.rs @@ -57,18 +57,19 @@ impl fmt::Display for MethodDef { write!(f, "{}", arg)?; needs_comma = true; } - writeln!(f, ") -> {}:", self.r#return)?; + write!(f, ") -> {}:", self.r#return)?; let doc = self.doc; if !doc.is_empty() { - writeln!(f, r#"{indent}{indent}r""""#)?; + writeln!(f)?; + write!(f, r#"{indent}{indent}r""""#)?; for line in doc.lines() { writeln!(f, "{indent}{indent}{}", line)?; } writeln!(f, r#"{indent}{indent}""""#)?; + } else { + writeln!(f, " ...")?; } - writeln!(f, "{indent}{indent}...")?; - writeln!(f)?; Ok(()) } } From 05340ce622f69cd71546dc450c57c8103cdcfa35 Mon Sep 17 00:00:00 2001 From: Yannick Molinghen Date: Wed, 19 Feb 2025 15:43:19 +0100 Subject: [PATCH 05/10] Unnecessary dots when there is a docstring --- pyo3-stub-gen/src/generate/method.rs | 2 +- pyo3-stub-gen/src/lib.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/pyo3-stub-gen/src/generate/method.rs b/pyo3-stub-gen/src/generate/method.rs index 7d30ffb..f3364fb 100644 --- a/pyo3-stub-gen/src/generate/method.rs +++ b/pyo3-stub-gen/src/generate/method.rs @@ -62,7 +62,7 @@ impl fmt::Display for MethodDef { let doc = self.doc; if !doc.is_empty() { writeln!(f)?; - write!(f, r#"{indent}{indent}r""""#)?; + writeln!(f, r#"{indent}{indent}r""""#)?; for line in doc.lines() { writeln!(f, "{indent}{indent}{}", line)?; } diff --git a/pyo3-stub-gen/src/lib.rs b/pyo3-stub-gen/src/lib.rs index 1116453..c2ad975 100644 --- a/pyo3-stub-gen/src/lib.rs +++ b/pyo3-stub-gen/src/lib.rs @@ -135,7 +135,6 @@ //! r""" //! This is a foo method. //! """ -//! ... //! "#.trim() //! ); //! ``` From 2e1a1bd1ed0122ef6301e199e994a817ae20d09e Mon Sep 17 00:00:00 2001 From: Yannick Molinghen Date: Wed, 19 Feb 2025 15:44:31 +0100 Subject: [PATCH 06/10] Updated examples --- examples/mixed/python/mixed/main_mod.pyi | 6 ++-- .../python/mixed_sub/main_mod/__init__.pyi | 6 ++-- .../python/mixed_sub/main_mod/int.pyi | 3 +- .../python/mixed_sub/main_mod/sub_mod.pyi | 3 +- .../mixed_sub_multiple/main_mod/__init__.pyi | 3 +- .../mixed_sub_multiple/main_mod/mod_a.pyi | 3 +- .../mixed_sub_multiple/main_mod/mod_b.pyi | 3 +- examples/pure/pure.pyi | 28 +++++++++++-------- 8 files changed, 31 insertions(+), 24 deletions(-) diff --git a/examples/mixed/python/mixed/main_mod.pyi b/examples/mixed/python/mixed/main_mod.pyi index 2e62eb6..4209205 100644 --- a/examples/mixed/python/mixed/main_mod.pyi +++ b/examples/mixed/python/mixed/main_mod.pyi @@ -9,5 +9,7 @@ class A: class B: def show_x(self) -> None: ... -def create_a(x: builtins.int) -> A: ... -def create_b(x: builtins.int) -> B: ... +def create_a(x:builtins.int) -> A: ... + +def create_b(x:builtins.int) -> B: ... + diff --git a/examples/mixed_sub/python/mixed_sub/main_mod/__init__.pyi b/examples/mixed_sub/python/mixed_sub/main_mod/__init__.pyi index c051c6d..9378bdb 100644 --- a/examples/mixed_sub/python/mixed_sub/main_mod/__init__.pyi +++ b/examples/mixed_sub/python/mixed_sub/main_mod/__init__.pyi @@ -11,5 +11,7 @@ class A: class B: def show_x(self) -> None: ... -def create_a(x: builtins.int) -> A: ... -def create_b(x: builtins.int) -> B: ... +def create_a(x:builtins.int) -> A: ... + +def create_b(x:builtins.int) -> B: ... + diff --git a/examples/mixed_sub/python/mixed_sub/main_mod/int.pyi b/examples/mixed_sub/python/mixed_sub/main_mod/int.pyi index f36463b..1cc6213 100644 --- a/examples/mixed_sub/python/mixed_sub/main_mod/int.pyi +++ b/examples/mixed_sub/python/mixed_sub/main_mod/int.pyi @@ -3,4 +3,5 @@ import builtins -def dummy_int_fun(x: builtins.int) -> builtins.int: ... +def dummy_int_fun(x:builtins.int) -> builtins.int: ... + diff --git a/examples/mixed_sub/python/mixed_sub/main_mod/sub_mod.pyi b/examples/mixed_sub/python/mixed_sub/main_mod/sub_mod.pyi index c78bc77..a792b86 100644 --- a/examples/mixed_sub/python/mixed_sub/main_mod/sub_mod.pyi +++ b/examples/mixed_sub/python/mixed_sub/main_mod/sub_mod.pyi @@ -6,4 +6,5 @@ import builtins class C: def show_x(self) -> None: ... -def create_c(x: builtins.int) -> C: ... +def create_c(x:builtins.int) -> C: ... + diff --git a/examples/mixed_sub_multiple/python/mixed_sub_multiple/main_mod/__init__.pyi b/examples/mixed_sub_multiple/python/mixed_sub_multiple/main_mod/__init__.pyi index d90215d..8e66f45 100644 --- a/examples/mixed_sub_multiple/python/mixed_sub_multiple/main_mod/__init__.pyi +++ b/examples/mixed_sub_multiple/python/mixed_sub_multiple/main_mod/__init__.pyi @@ -4,6 +4,5 @@ from . import mod_a from . import mod_b -def greet_main() -> None: - ... +def greet_main() -> None: ... diff --git a/examples/mixed_sub_multiple/python/mixed_sub_multiple/main_mod/mod_a.pyi b/examples/mixed_sub_multiple/python/mixed_sub_multiple/main_mod/mod_a.pyi index d475518..3115a5f 100644 --- a/examples/mixed_sub_multiple/python/mixed_sub_multiple/main_mod/mod_a.pyi +++ b/examples/mixed_sub_multiple/python/mixed_sub_multiple/main_mod/mod_a.pyi @@ -2,6 +2,5 @@ # ruff: noqa: E501, F401 -def greet_a() -> None: - ... +def greet_a() -> None: ... diff --git a/examples/mixed_sub_multiple/python/mixed_sub_multiple/main_mod/mod_b.pyi b/examples/mixed_sub_multiple/python/mixed_sub_multiple/main_mod/mod_b.pyi index 291f470..d35196b 100644 --- a/examples/mixed_sub_multiple/python/mixed_sub_multiple/main_mod/mod_b.pyi +++ b/examples/mixed_sub_multiple/python/mixed_sub_multiple/main_mod/mod_b.pyi @@ -2,6 +2,5 @@ # ruff: noqa: E501, F401 -def greet_b() -> None: - ... +def greet_b() -> None: ... diff --git a/examples/pure/pure.pyi b/examples/pure/pure.pyi index d588a43..0919991 100644 --- a/examples/pure/pure.pyi +++ b/examples/pure/pure.pyi @@ -8,12 +8,11 @@ import typing from enum import Enum, auto MY_CONSTANT: builtins.int - class A: x: builtins.int - def __new__(cls, x: builtins.int): ... + def __new__(cls,x:builtins.int): ... def show_x(self) -> None: ... - def ref_test(self, x: dict) -> dict: ... + def ref_test(self, x:dict) -> dict: ... class Number(Enum): FLOAT = auto() @@ -30,19 +29,24 @@ class Number(Enum): ... def ahash_dict() -> builtins.dict[builtins.str, builtins.int]: ... -def create_a(x: builtins.int = 2) -> A: ... -def create_dict(n: builtins.int) -> builtins.dict[builtins.int, builtins.list[builtins.int]]: ... -def default_value(num: Number = ...) -> Number: ... -def echo_path(path: builtins.str | os.PathLike | pathlib.Path) -> builtins.str: ... -def read_dict(dict: typing.Mapping[builtins.int, typing.Mapping[builtins.int, builtins.int]]) -> None: ... -def str_len(x: builtins.str) -> builtins.int: - r""" + +def create_a(x:builtins.int=2) -> A: ... + +def create_dict(n:builtins.int) -> builtins.dict[builtins.int, builtins.list[builtins.int]]: ... + +def default_value(num:Number=...) -> Number: ... + +def echo_path(path:builtins.str | os.PathLike | pathlib.Path) -> builtins.str: ... + +def read_dict(dict:typing.Mapping[builtins.int, typing.Mapping[builtins.int, builtins.int]]) -> None: ... + +def str_len(x:builtins.str) -> builtins.int: r""" Returns the length of the string. """ -def sum(v: typing.Sequence[builtins.int]) -> builtins.int: - r""" +def sum(v:typing.Sequence[builtins.int]) -> builtins.int: r""" Returns the sum of two numbers as a string. """ class MyError(RuntimeError): ... + From ad4903cc1db48942582e58f8453920e176239ed4 Mon Sep 17 00:00:00 2001 From: Yannick Molinghen Date: Wed, 19 Feb 2025 15:59:38 +0100 Subject: [PATCH 07/10] Removed docs on members --- examples/pure/pure.pyi | 2 -- pyo3-stub-gen/src/generate/member.rs | 11 ----------- pyo3-stub-gen/src/generate/stub_info.rs | 2 -- pyo3-stub-gen/src/lib.rs | 6 ++---- 4 files changed, 2 insertions(+), 19 deletions(-) diff --git a/examples/pure/pure.pyi b/examples/pure/pure.pyi index 0919991..a3afe32 100644 --- a/examples/pure/pure.pyi +++ b/examples/pure/pure.pyi @@ -20,12 +20,10 @@ class Number(Enum): @property def is_float(self) -> builtins.bool: - """Whether the number is a float.""" ... @property def is_integer(self) -> builtins.bool: - """Whether the number is an integer.""" ... def ahash_dict() -> builtins.dict[builtins.str, builtins.int]: ... diff --git a/pyo3-stub-gen/src/generate/member.rs b/pyo3-stub-gen/src/generate/member.rs index 1f3fca6..2e3dbeb 100644 --- a/pyo3-stub-gen/src/generate/member.rs +++ b/pyo3-stub-gen/src/generate/member.rs @@ -1,5 +1,3 @@ -use itertools::Itertools; - use crate::{generate::*, type_info::*, TypeInfo}; use std::{ collections::HashSet, @@ -12,7 +10,6 @@ pub struct MemberDef { pub is_property: bool, pub name: &'static str, pub r#type: TypeInfo, - pub doc: &'static str, } impl Import for MemberDef { @@ -27,7 +24,6 @@ impl From<&MemberInfo> for MemberDef { is_property: false, name: info.name, r#type: (info.r#type)(), - doc: info.doc, } } } @@ -35,19 +31,12 @@ impl From<&MemberInfo> for MemberDef { impl fmt::Display for MemberDef { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let indent = indent(); - let doc = self.doc.split("\n").join(&format!("\n{indent}")); if self.is_property { writeln!(f, "{indent}@property")?; writeln!(f, "{indent}def {}(self) -> {}:", self.name, self.r#type)?; - if !doc.is_empty() { - writeln!(f, r#"{indent} """{doc}""""#)?; - } writeln!(f, "{indent} ...") } else { writeln!(f, "{indent}{}: {}", self.name, self.r#type)?; - if !doc.is_empty() { - writeln!(f, r#"{indent}"""{doc}""""#)?; - } Ok(()) } } diff --git a/pyo3-stub-gen/src/generate/stub_info.rs b/pyo3-stub-gen/src/generate/stub_info.rs index 96f285a..8100665 100644 --- a/pyo3-stub-gen/src/generate/stub_info.rs +++ b/pyo3-stub-gen/src/generate/stub_info.rs @@ -130,7 +130,6 @@ impl StubInfoBuilder { is_property: false, name: getter.name, r#type: (getter.r#type)(), - doc: getter.doc, }); } for method in info.methods { @@ -146,7 +145,6 @@ impl StubInfoBuilder { is_property: true, name: getter.name, r#type: (getter.r#type)(), - doc: getter.doc, }); } for method in info.methods { diff --git a/pyo3-stub-gen/src/lib.rs b/pyo3-stub-gen/src/lib.rs index c2ad975..3875ba4 100644 --- a/pyo3-stub-gen/src/lib.rs +++ b/pyo3-stub-gen/src/lib.rs @@ -40,12 +40,12 @@ //! MemberInfo { //! name: "name", //! r#type: ::type_output, -//! doc: "Name docstring", +//! doc: "", //! }, //! MemberInfo { //! name: "description", //! r#type: as ::pyo3_stub_gen::PyStubType>::type_output, -//! doc: "Description docstring", +//! doc: "", //! }, //! ], //! doc: "Docstring used in Python", @@ -61,9 +61,7 @@ //! Docstring used in Python //! """ //! name: str -//! """Name docstring""" //! description: Optional[str] -//! """Description docstring""" //! ``` //! //! We want to generate this [type_info::PyClassInfo] section automatically from `MyClass` Rust struct definition. From 854e9892174b892eab36617f84527c172166fb6a Mon Sep 17 00:00:00 2001 From: Yannick Molinghen Date: Wed, 19 Feb 2025 16:02:34 +0100 Subject: [PATCH 08/10] Removed property annotation --- pyo3-stub-gen/src/generate/member.rs | 11 +---------- pyo3-stub-gen/src/generate/stub_info.rs | 2 -- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/pyo3-stub-gen/src/generate/member.rs b/pyo3-stub-gen/src/generate/member.rs index 2e3dbeb..852a87f 100644 --- a/pyo3-stub-gen/src/generate/member.rs +++ b/pyo3-stub-gen/src/generate/member.rs @@ -7,7 +7,6 @@ use std::{ /// Definition of a class member. #[derive(Debug, Clone, PartialEq)] pub struct MemberDef { - pub is_property: bool, pub name: &'static str, pub r#type: TypeInfo, } @@ -21,7 +20,6 @@ impl Import for MemberDef { impl From<&MemberInfo> for MemberDef { fn from(info: &MemberInfo) -> Self { Self { - is_property: false, name: info.name, r#type: (info.r#type)(), } @@ -31,13 +29,6 @@ impl From<&MemberInfo> for MemberDef { impl fmt::Display for MemberDef { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let indent = indent(); - if self.is_property { - writeln!(f, "{indent}@property")?; - writeln!(f, "{indent}def {}(self) -> {}:", self.name, self.r#type)?; - writeln!(f, "{indent} ...") - } else { - writeln!(f, "{indent}{}: {}", self.name, self.r#type)?; - Ok(()) - } + writeln!(f, "{indent}{}: {}", self.name, self.r#type) } } diff --git a/pyo3-stub-gen/src/generate/stub_info.rs b/pyo3-stub-gen/src/generate/stub_info.rs index 8100665..5f2a2ea 100644 --- a/pyo3-stub-gen/src/generate/stub_info.rs +++ b/pyo3-stub-gen/src/generate/stub_info.rs @@ -127,7 +127,6 @@ impl StubInfoBuilder { if let Some(entry) = module.class.get_mut(&struct_id) { for getter in info.getters { entry.members.push(MemberDef { - is_property: false, name: getter.name, r#type: (getter.r#type)(), }); @@ -142,7 +141,6 @@ impl StubInfoBuilder { } else if let Some(entry) = module.enum_.get_mut(&struct_id) { for getter in info.getters { entry.members.push(MemberDef { - is_property: true, name: getter.name, r#type: (getter.r#type)(), }); From 272cbeec3b99a9f811fbb7120ea29b034c06f28f Mon Sep 17 00:00:00 2001 From: Yannick Molinghen Date: Wed, 19 Feb 2025 16:07:53 +0100 Subject: [PATCH 09/10] Revert "Removed property annotation" This reverts commit 854e9892174b892eab36617f84527c172166fb6a. --- pyo3-stub-gen/src/generate/member.rs | 11 ++++++++++- pyo3-stub-gen/src/generate/stub_info.rs | 2 ++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/pyo3-stub-gen/src/generate/member.rs b/pyo3-stub-gen/src/generate/member.rs index 852a87f..2e3dbeb 100644 --- a/pyo3-stub-gen/src/generate/member.rs +++ b/pyo3-stub-gen/src/generate/member.rs @@ -7,6 +7,7 @@ use std::{ /// Definition of a class member. #[derive(Debug, Clone, PartialEq)] pub struct MemberDef { + pub is_property: bool, pub name: &'static str, pub r#type: TypeInfo, } @@ -20,6 +21,7 @@ impl Import for MemberDef { impl From<&MemberInfo> for MemberDef { fn from(info: &MemberInfo) -> Self { Self { + is_property: false, name: info.name, r#type: (info.r#type)(), } @@ -29,6 +31,13 @@ impl From<&MemberInfo> for MemberDef { impl fmt::Display for MemberDef { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let indent = indent(); - writeln!(f, "{indent}{}: {}", self.name, self.r#type) + if self.is_property { + writeln!(f, "{indent}@property")?; + writeln!(f, "{indent}def {}(self) -> {}:", self.name, self.r#type)?; + writeln!(f, "{indent} ...") + } else { + writeln!(f, "{indent}{}: {}", self.name, self.r#type)?; + Ok(()) + } } } diff --git a/pyo3-stub-gen/src/generate/stub_info.rs b/pyo3-stub-gen/src/generate/stub_info.rs index 5f2a2ea..8100665 100644 --- a/pyo3-stub-gen/src/generate/stub_info.rs +++ b/pyo3-stub-gen/src/generate/stub_info.rs @@ -127,6 +127,7 @@ impl StubInfoBuilder { if let Some(entry) = module.class.get_mut(&struct_id) { for getter in info.getters { entry.members.push(MemberDef { + is_property: false, name: getter.name, r#type: (getter.r#type)(), }); @@ -141,6 +142,7 @@ impl StubInfoBuilder { } else if let Some(entry) = module.enum_.get_mut(&struct_id) { for getter in info.getters { entry.members.push(MemberDef { + is_property: true, name: getter.name, r#type: (getter.r#type)(), }); From 613cd1aed7da7fd90c37c42776e83861f15dfdd2 Mon Sep 17 00:00:00 2001 From: Yannick Molinghen Date: Wed, 19 Feb 2025 16:08:57 +0100 Subject: [PATCH 10/10] Revert "Removed docs on members" This reverts commit ad4903cc1db48942582e58f8453920e176239ed4. --- examples/pure/pure.pyi | 2 ++ pyo3-stub-gen/src/generate/member.rs | 11 +++++++++++ pyo3-stub-gen/src/generate/stub_info.rs | 2 ++ pyo3-stub-gen/src/lib.rs | 6 ++++-- 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/examples/pure/pure.pyi b/examples/pure/pure.pyi index a3afe32..0919991 100644 --- a/examples/pure/pure.pyi +++ b/examples/pure/pure.pyi @@ -20,10 +20,12 @@ class Number(Enum): @property def is_float(self) -> builtins.bool: + """Whether the number is a float.""" ... @property def is_integer(self) -> builtins.bool: + """Whether the number is an integer.""" ... def ahash_dict() -> builtins.dict[builtins.str, builtins.int]: ... diff --git a/pyo3-stub-gen/src/generate/member.rs b/pyo3-stub-gen/src/generate/member.rs index 2e3dbeb..1f3fca6 100644 --- a/pyo3-stub-gen/src/generate/member.rs +++ b/pyo3-stub-gen/src/generate/member.rs @@ -1,3 +1,5 @@ +use itertools::Itertools; + use crate::{generate::*, type_info::*, TypeInfo}; use std::{ collections::HashSet, @@ -10,6 +12,7 @@ pub struct MemberDef { pub is_property: bool, pub name: &'static str, pub r#type: TypeInfo, + pub doc: &'static str, } impl Import for MemberDef { @@ -24,6 +27,7 @@ impl From<&MemberInfo> for MemberDef { is_property: false, name: info.name, r#type: (info.r#type)(), + doc: info.doc, } } } @@ -31,12 +35,19 @@ impl From<&MemberInfo> for MemberDef { impl fmt::Display for MemberDef { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let indent = indent(); + let doc = self.doc.split("\n").join(&format!("\n{indent}")); if self.is_property { writeln!(f, "{indent}@property")?; writeln!(f, "{indent}def {}(self) -> {}:", self.name, self.r#type)?; + if !doc.is_empty() { + writeln!(f, r#"{indent} """{doc}""""#)?; + } writeln!(f, "{indent} ...") } else { writeln!(f, "{indent}{}: {}", self.name, self.r#type)?; + if !doc.is_empty() { + writeln!(f, r#"{indent}"""{doc}""""#)?; + } Ok(()) } } diff --git a/pyo3-stub-gen/src/generate/stub_info.rs b/pyo3-stub-gen/src/generate/stub_info.rs index 8100665..96f285a 100644 --- a/pyo3-stub-gen/src/generate/stub_info.rs +++ b/pyo3-stub-gen/src/generate/stub_info.rs @@ -130,6 +130,7 @@ impl StubInfoBuilder { is_property: false, name: getter.name, r#type: (getter.r#type)(), + doc: getter.doc, }); } for method in info.methods { @@ -145,6 +146,7 @@ impl StubInfoBuilder { is_property: true, name: getter.name, r#type: (getter.r#type)(), + doc: getter.doc, }); } for method in info.methods { diff --git a/pyo3-stub-gen/src/lib.rs b/pyo3-stub-gen/src/lib.rs index 3875ba4..c2ad975 100644 --- a/pyo3-stub-gen/src/lib.rs +++ b/pyo3-stub-gen/src/lib.rs @@ -40,12 +40,12 @@ //! MemberInfo { //! name: "name", //! r#type: ::type_output, -//! doc: "", +//! doc: "Name docstring", //! }, //! MemberInfo { //! name: "description", //! r#type: as ::pyo3_stub_gen::PyStubType>::type_output, -//! doc: "", +//! doc: "Description docstring", //! }, //! ], //! doc: "Docstring used in Python", @@ -61,7 +61,9 @@ //! Docstring used in Python //! """ //! name: str +//! """Name docstring""" //! description: Optional[str] +//! """Description docstring""" //! ``` //! //! We want to generate this [type_info::PyClassInfo] section automatically from `MyClass` Rust struct definition.