From 1a7516a0a373cb228755ae89962937dd4bdb184b Mon Sep 17 00:00:00 2001 From: Markus Pielmeier Date: Tue, 21 Apr 2026 09:19:34 +0200 Subject: [PATCH 1/4] Implement attribute docstrings --- pybind11_stubgen/__init__.py | 2 ++ pybind11_stubgen/parser/mixins/fix.py | 13 +++++++++++++ pybind11_stubgen/printer.py | 5 ++++- pybind11_stubgen/structs.py | 1 + 4 files changed, 20 insertions(+), 1 deletion(-) diff --git a/pybind11_stubgen/__init__.py b/pybind11_stubgen/__init__.py index 2e96becd..02adaed5 100644 --- a/pybind11_stubgen/__init__.py +++ b/pybind11_stubgen/__init__.py @@ -32,6 +32,7 @@ FixMissing__all__Attribute, FixMissing__future__AnnotationsImport, FixMissingEnumMembersAnnotation, + FixMissingFieldDocString, FixMissingFixedSizeImport, FixMissingImports, FixMissingNoneHashFieldAnnotation, @@ -270,6 +271,7 @@ class Parser( FixPEP585CollectionNames, FixTypingTypeNames, FixScipyTypeArguments, + FixMissingFieldDocString, FixMissingFixedSizeImport, FixMissingEnumMembersAnnotation, OverridePrintSafeValues, diff --git a/pybind11_stubgen/parser/mixins/fix.py b/pybind11_stubgen/parser/mixins/fix.py index 595c15e1..b248d9ed 100644 --- a/pybind11_stubgen/parser/mixins/fix.py +++ b/pybind11_stubgen/parser/mixins/fix.py @@ -964,6 +964,19 @@ def handle_class_member( return result +class FixMissingFieldDocString(IParser): + def handle_class_member( + self, path: QualifiedName, class_: type, obj: Any + ) -> Docstring | Alias | Class | list[Method] | Field | Property | None: + result = super().handle_class_member(path, class_, obj) + if isinstance(result, Field): + obj2 = class_.__dict__[path[-1]] + doc = getattr(obj2, "__doc__", None) + if obj is not obj2 and isinstance(doc, str): + result.attribute.doc = Docstring(doc) + return result + + class FixMissingFixedSizeImport(IParser): def parse_annotation_str( self, annotation_str: str diff --git a/pybind11_stubgen/printer.py b/pybind11_stubgen/printer.py index cb1bf904..2361500f 100644 --- a/pybind11_stubgen/printer.py +++ b/pybind11_stubgen/printer.py @@ -166,7 +166,10 @@ def print_attribute(self, attr: Attribute) -> list[str]: if attr.value is not None and self.print_value_comments: parts.append(f" # value = {self.print_value(attr.value)}") - return ["".join(parts)] + result = ["".join(parts)] + if attr.doc: + result.extend(self.print_docstring(attr.doc)) + return result def print_argument(self, arg: Argument) -> str: parts = [] diff --git a/pybind11_stubgen/structs.py b/pybind11_stubgen/structs.py index 79ad65c6..76690665 100644 --- a/pybind11_stubgen/structs.py +++ b/pybind11_stubgen/structs.py @@ -108,6 +108,7 @@ class Attribute: name: Identifier value: Value | None annotation: Annotation | None = field_(default=None) + doc: Docstring | None = field_(default=None) @dataclass From 8757191d8b1e37564ec32906c2274900383eb8d1 Mon Sep 17 00:00:00 2001 From: Markus Pielmeier Date: Tue, 21 Apr 2026 10:09:47 +0200 Subject: [PATCH 2/4] Regenerate stubs --- .../demo/_bindings/properties.pyi | 18 ++++++++++++++++++ .../demo/_bindings/properties.pyi | 18 ++++++++++++++++++ .../demo/_bindings/properties.pyi | 18 ++++++++++++++++++ .../demo/_bindings/properties.pyi | 18 ++++++++++++++++++ .../demo/_bindings/properties.pyi | 18 ++++++++++++++++++ .../demo/_bindings/properties.pyi | 18 ++++++++++++++++++ 6 files changed, 108 insertions(+) diff --git a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi b/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi index f19453b4..e47254e8 100644 --- a/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi +++ b/tests/stubs/python-3.12/pybind11-v2.11/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi @@ -29,7 +29,13 @@ class WithPropDoc: """ def_property_readonly_static: typing.ClassVar[int] = 0 + """ + prop doc token + """ def_property_static: typing.ClassVar[int] = 0 + """ + prop doc token + """ @property def def_property(self) -> int: """ @@ -61,7 +67,13 @@ class WithGetterSetterDoc: """ def_property_readonly_static: typing.ClassVar[int] = 0 + """ + getter doc token + """ def_property_static: typing.ClassVar[int] = 0 + """ + getter doc token + """ @property def def_property(self) -> int: """ @@ -84,7 +96,13 @@ class WithPropAndGetterSetterDoc: """ def_property_readonly_static: typing.ClassVar[int] = 0 + """ + prop doc token + """ def_property_static: typing.ClassVar[int] = 0 + """ + prop doc token + """ @property def def_property(self) -> int: """ diff --git a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi b/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi index f19453b4..e47254e8 100644 --- a/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi +++ b/tests/stubs/python-3.12/pybind11-v2.12/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi @@ -29,7 +29,13 @@ class WithPropDoc: """ def_property_readonly_static: typing.ClassVar[int] = 0 + """ + prop doc token + """ def_property_static: typing.ClassVar[int] = 0 + """ + prop doc token + """ @property def def_property(self) -> int: """ @@ -61,7 +67,13 @@ class WithGetterSetterDoc: """ def_property_readonly_static: typing.ClassVar[int] = 0 + """ + getter doc token + """ def_property_static: typing.ClassVar[int] = 0 + """ + getter doc token + """ @property def def_property(self) -> int: """ @@ -84,7 +96,13 @@ class WithPropAndGetterSetterDoc: """ def_property_readonly_static: typing.ClassVar[int] = 0 + """ + prop doc token + """ def_property_static: typing.ClassVar[int] = 0 + """ + prop doc token + """ @property def def_property(self) -> int: """ diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/properties.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/properties.pyi index f19453b4..e47254e8 100644 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/properties.pyi +++ b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-use-type-var/demo/_bindings/properties.pyi @@ -29,7 +29,13 @@ class WithPropDoc: """ def_property_readonly_static: typing.ClassVar[int] = 0 + """ + prop doc token + """ def_property_static: typing.ClassVar[int] = 0 + """ + prop doc token + """ @property def def_property(self) -> int: """ @@ -61,7 +67,13 @@ class WithGetterSetterDoc: """ def_property_readonly_static: typing.ClassVar[int] = 0 + """ + getter doc token + """ def_property_static: typing.ClassVar[int] = 0 + """ + getter doc token + """ @property def def_property(self) -> int: """ @@ -84,7 +96,13 @@ class WithPropAndGetterSetterDoc: """ def_property_readonly_static: typing.ClassVar[int] = 0 + """ + prop doc token + """ def_property_static: typing.ClassVar[int] = 0 + """ + prop doc token + """ @property def def_property(self) -> int: """ diff --git a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi index f19453b4..e47254e8 100644 --- a/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi +++ b/tests/stubs/python-3.12/pybind11-v2.13/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi @@ -29,7 +29,13 @@ class WithPropDoc: """ def_property_readonly_static: typing.ClassVar[int] = 0 + """ + prop doc token + """ def_property_static: typing.ClassVar[int] = 0 + """ + prop doc token + """ @property def def_property(self) -> int: """ @@ -61,7 +67,13 @@ class WithGetterSetterDoc: """ def_property_readonly_static: typing.ClassVar[int] = 0 + """ + getter doc token + """ def_property_static: typing.ClassVar[int] = 0 + """ + getter doc token + """ @property def def_property(self) -> int: """ @@ -84,7 +96,13 @@ class WithPropAndGetterSetterDoc: """ def_property_readonly_static: typing.ClassVar[int] = 0 + """ + prop doc token + """ def_property_static: typing.ClassVar[int] = 0 + """ + prop doc token + """ @property def def_property(self) -> int: """ diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/properties.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/properties.pyi index e24ef6ac..eb99e55f 100644 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/properties.pyi +++ b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-use-type-var/demo/_bindings/properties.pyi @@ -37,7 +37,13 @@ class WithPropDoc: """ def_property_readonly_static: typing.ClassVar[int] = 0 + """ + prop doc token + """ def_property_static: typing.ClassVar[int] = 0 + """ + prop doc token + """ @property def def_property(self) -> int: """ @@ -71,7 +77,13 @@ class WithGetterSetterDoc: """ def_property_readonly_static: typing.ClassVar[int] = 0 + """ + getter doc token + """ def_property_static: typing.ClassVar[int] = 0 + """ + getter doc token + """ @property def def_property(self) -> int: """ @@ -94,7 +106,13 @@ class WithPropAndGetterSetterDoc: """ def_property_readonly_static: typing.ClassVar[int] = 0 + """ + prop doc token + """ def_property_static: typing.ClassVar[int] = 0 + """ + prop doc token + """ @property def def_property(self) -> int: """ diff --git a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi index e24ef6ac..eb99e55f 100644 --- a/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi +++ b/tests/stubs/python-3.12/pybind11-v3.0/numpy-array-wrap-with-annotated/demo/_bindings/properties.pyi @@ -37,7 +37,13 @@ class WithPropDoc: """ def_property_readonly_static: typing.ClassVar[int] = 0 + """ + prop doc token + """ def_property_static: typing.ClassVar[int] = 0 + """ + prop doc token + """ @property def def_property(self) -> int: """ @@ -71,7 +77,13 @@ class WithGetterSetterDoc: """ def_property_readonly_static: typing.ClassVar[int] = 0 + """ + getter doc token + """ def_property_static: typing.ClassVar[int] = 0 + """ + getter doc token + """ @property def def_property(self) -> int: """ @@ -94,7 +106,13 @@ class WithPropAndGetterSetterDoc: """ def_property_readonly_static: typing.ClassVar[int] = 0 + """ + prop doc token + """ def_property_static: typing.ClassVar[int] = 0 + """ + prop doc token + """ @property def def_property(self) -> int: """ From 7fcbdf8465d63dd238f7563379e018d703abbaae Mon Sep 17 00:00:00 2001 From: Markus Pielmeier Date: Mon, 27 Apr 2026 12:04:47 +0200 Subject: [PATCH 3/4] Add comments --- pybind11_stubgen/parser/mixins/fix.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pybind11_stubgen/parser/mixins/fix.py b/pybind11_stubgen/parser/mixins/fix.py index b248d9ed..a60676ee 100644 --- a/pybind11_stubgen/parser/mixins/fix.py +++ b/pybind11_stubgen/parser/mixins/fix.py @@ -965,13 +965,21 @@ def handle_class_member( class FixMissingFieldDocString(IParser): + """Extracts docstrings for `def_property_readonly_static` and `def_property_static`.""" + def handle_class_member( self, path: QualifiedName, class_: type, obj: Any ) -> Docstring | Alias | Class | list[Method] | Field | Property | None: result = super().handle_class_member(path, class_, obj) + + # `ParserDispatchMixin.handle_class_member` classifies static properties as `Field` instead of `Property`. if isinstance(result, Field): obj2 = class_.__dict__[path[-1]] doc = getattr(obj2, "__doc__", None) + + # If the current item is a static property, `obj` contains the fully resolved value, + # but `obj2` contains a `pybind11_builtins.pybind11_static_property` proxy object. + # In Python 3.12+, this proxy object has a `__doc__` attribute. if obj is not obj2 and isinstance(doc, str): result.attribute.doc = Docstring(doc) return result From 982875b19288c8485c03acd904046fed7f1bba20 Mon Sep 17 00:00:00 2001 From: Markus Pielmeier Date: Tue, 28 Apr 2026 09:30:51 +0200 Subject: [PATCH 4/4] Reformat using ruff --- pybind11_stubgen/parser/mixins/fix.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pybind11_stubgen/parser/mixins/fix.py b/pybind11_stubgen/parser/mixins/fix.py index a60676ee..57ade355 100644 --- a/pybind11_stubgen/parser/mixins/fix.py +++ b/pybind11_stubgen/parser/mixins/fix.py @@ -966,18 +966,18 @@ def handle_class_member( class FixMissingFieldDocString(IParser): """Extracts docstrings for `def_property_readonly_static` and `def_property_static`.""" - + def handle_class_member( self, path: QualifiedName, class_: type, obj: Any ) -> Docstring | Alias | Class | list[Method] | Field | Property | None: result = super().handle_class_member(path, class_, obj) - + # `ParserDispatchMixin.handle_class_member` classifies static properties as `Field` instead of `Property`. if isinstance(result, Field): obj2 = class_.__dict__[path[-1]] doc = getattr(obj2, "__doc__", None) - - # If the current item is a static property, `obj` contains the fully resolved value, + + # If the current item is a static property, `obj` contains the fully resolved value, # but `obj2` contains a `pybind11_builtins.pybind11_static_property` proxy object. # In Python 3.12+, this proxy object has a `__doc__` attribute. if obj is not obj2 and isinstance(doc, str):