Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions pybind11_stubgen/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
FixMissing__all__Attribute,
FixMissing__future__AnnotationsImport,
FixMissingEnumMembersAnnotation,
FixMissingFieldDocString,
FixMissingFixedSizeImport,
FixMissingImports,
FixMissingNoneHashFieldAnnotation,
Expand Down Expand Up @@ -270,6 +271,7 @@ class Parser(
FixPEP585CollectionNames,
FixTypingTypeNames,
FixScipyTypeArguments,
FixMissingFieldDocString,
FixMissingFixedSizeImport,
FixMissingEnumMembersAnnotation,
OverridePrintSafeValues,
Expand Down
21 changes: 21 additions & 0 deletions pybind11_stubgen/parser/mixins/fix.py
Original file line number Diff line number Diff line change
Expand Up @@ -964,6 +964,27 @@ def handle_class_member(
return result


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):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any specific reason static properties fall under Field in the class processor? Without too much in-depth analysis, this looks like a bug that we are trying to circumvent here.

Copy link
Copy Markdown
Contributor Author

@markuspi markuspi Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess since class-level/static properties don't really exist in plain python, pybind11-stubgen's Property might not be suited since it is rendered using @property. So Field might be the next-best choice.

ParserDispatchMixin._iter_module_members mimics inspect.getmembers (before #238 it directly used inspect.getmembers), which already resolves def_property_readonly_static and def_property_static to their final values, thus losing the information about a descriptor. Presumably, we could change the logic of ParserDispatchMixin._iter_module_members to mimic inspect.getmembers_static, but I don't know which problems this will cause with other function types.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are usually done as a combo of @classmethod and @Property decorators in plain Python. I don't know what pybind11 uses internally for those (as alternatively, this can be a metaclass as well). I think we can leave this as is, but a comment in the code explaining this logic (why we check against Field) would be nice.

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


class FixMissingFixedSizeImport(IParser):
def parse_annotation_str(
self, annotation_str: str
Expand Down
5 changes: 4 additions & 1 deletion pybind11_stubgen/printer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand Down
1 change: 1 addition & 0 deletions pybind11_stubgen/structs.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ class Attribute:
name: Identifier
value: Value | None
annotation: Annotation | None = field_(default=None)
doc: Docstring | None = field_(default=None)


@dataclass
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
"""
Expand Down Expand Up @@ -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:
"""
Expand All @@ -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:
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
"""
Expand Down Expand Up @@ -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:
"""
Expand All @@ -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:
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
"""
Expand Down Expand Up @@ -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:
"""
Expand All @@ -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:
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
"""
Expand Down Expand Up @@ -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:
"""
Expand All @@ -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:
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
"""
Expand Down Expand Up @@ -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:
"""
Expand All @@ -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:
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
"""
Expand Down Expand Up @@ -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:
"""
Expand All @@ -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:
"""
Expand Down
Loading