Skip to content

Commit 7a0db40

Browse files
committed
stubgenc: add support for including class and property docstrings
1 parent 256cf68 commit 7a0db40

File tree

5 files changed

+64
-22
lines changed

5 files changed

+64
-22
lines changed

mypy/stubdoc.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ class FunctionSig(NamedTuple):
7878
args: list[ArgSig]
7979
ret_type: str | None
8080
type_args: str = "" # TODO implement in stubgenc and remove the default
81+
docstring: str | None = None
8182

8283
def is_special_method(self) -> bool:
8384
return bool(
@@ -110,6 +111,7 @@ def format_sig(
110111
is_async: bool = False,
111112
any_val: str | None = None,
112113
docstring: str | None = None,
114+
include_docstrings: bool = False,
113115
) -> str:
114116
args: list[str] = []
115117
for arg in self.args:
@@ -144,8 +146,11 @@ def format_sig(
144146

145147
prefix = "async " if is_async else ""
146148
sig = f"{indent}{prefix}def {self.name}{self.type_args}({', '.join(args)}){retfield}:"
147-
if docstring:
148-
suffix = f"\n{indent} {mypy.util.quote_docstring(docstring)}"
149+
# if this object has a docstring it's probably produced by a SignatureGenerator, so it
150+
# takes precedence over the passed docstring, which acts as a fallback.
151+
doc = (self.docstring or docstring) if include_docstrings else None
152+
if doc:
153+
suffix = f"\n{indent} {mypy.util.quote_docstring(doc)}"
149154
else:
150155
suffix = " ..."
151156
return f"{sig}{suffix}"

mypy/stubgenc.py

+23-13
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
infer_method_arg_types,
3838
infer_method_ret_type,
3939
)
40+
from mypy.util import quote_docstring
4041

4142

4243
class ExternalSignatureGenerator(SignatureGenerator):
@@ -645,8 +646,7 @@ def generate_function_stub(
645646
if inferred[0].args and inferred[0].args[0].name == "cls":
646647
decorators.append("@classmethod")
647648

648-
if docstring:
649-
docstring = self._indent_docstring(docstring)
649+
docstring = self._indent_docstring(ctx.docstring) if ctx.docstring else None
650650
output.extend(self.format_func_def(inferred, decorators=decorators, docstring=docstring))
651651
self._fix_iter(ctx, inferred, output)
652652

@@ -750,9 +750,14 @@ def generate_property_stub(
750750
)
751751
else: # regular property
752752
if readonly:
753+
docstring = self._indent_docstring(ctx.docstring) if ctx.docstring else None
753754
ro_properties.append(f"{self._indent}@property")
754-
sig = FunctionSig(name, [ArgSig("self")], inferred_type)
755-
ro_properties.append(sig.format_sig(indent=self._indent))
755+
sig = FunctionSig(name, [ArgSig("self")], inferred_type, docstring=docstring)
756+
ro_properties.append(
757+
sig.format_sig(
758+
indent=self._indent, include_docstrings=self._include_docstrings
759+
)
760+
)
756761
else:
757762
if inferred_type is None:
758763
inferred_type = self.add_name("_typeshed.Incomplete")
@@ -867,8 +872,17 @@ def generate_class_stub(
867872
bases_str = "(%s)" % ", ".join(bases)
868873
else:
869874
bases_str = ""
870-
if types or static_properties or rw_properties or methods or ro_properties:
875+
876+
if class_info.docstring and self._include_docstrings:
877+
doc = quote_docstring(self._indent_docstring(class_info.docstring))
878+
doc = f" {self._indent}{doc}"
879+
docstring = doc.splitlines(keepends=False)
880+
else:
881+
docstring = []
882+
883+
if docstring or types or static_properties or rw_properties or methods or ro_properties:
871884
output.append(f"{self._indent}class {class_name}{bases_str}:")
885+
output.extend(docstring)
872886
for line in types:
873887
if (
874888
output
@@ -878,14 +892,10 @@ def generate_class_stub(
878892
):
879893
output.append("")
880894
output.append(line)
881-
for line in static_properties:
882-
output.append(line)
883-
for line in rw_properties:
884-
output.append(line)
885-
for line in methods:
886-
output.append(line)
887-
for line in ro_properties:
888-
output.append(line)
895+
output.extend(static_properties)
896+
output.extend(rw_properties)
897+
output.extend(methods)
898+
output.extend(ro_properties)
889899
else:
890900
output.append(f"{self._indent}class {class_name}{bases_str}: ...")
891901

mypy/stubutil.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -803,7 +803,8 @@ def format_func_def(
803803
signature.format_sig(
804804
indent=self._indent,
805805
is_async=is_coroutine,
806-
docstring=docstring if self._include_docstrings else None,
806+
docstring=docstring,
807+
include_docstrings=self._include_docstrings,
807808
)
808809
)
809810
return lines

test-data/pybind11_fixtures/expected_stubs_with_docs/pybind11_fixtures/__init__.pyi

+4-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,10 @@ class TestStruct:
3838
def __init__(self, *args, **kwargs) -> None:
3939
"""Initialize self. See help(type(self)) for accurate signature."""
4040
@property
41-
def field_readonly(self) -> int: ...
41+
def field_readonly(self) -> int:
42+
"""some docstring
43+
(arg0: pybind11_fixtures.TestStruct) -> int
44+
"""
4245

4346
def func_incomplete_signature(*args, **kwargs):
4447
"""func_incomplete_signature() -> dummy_sub_namespace::HasNoBinding"""

test-data/pybind11_fixtures/expected_stubs_with_docs/pybind11_fixtures/demo.pyi

+28-5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ __version__: str
55

66
class Point:
77
class AngleUnit:
8+
"""Members:
9+
10+
radian
11+
12+
degree"""
813
__members__: ClassVar[dict] = ... # read-only
914
__entries: ClassVar[dict] = ...
1015
degree: ClassVar[Point.AngleUnit] = ...
@@ -22,11 +27,23 @@ class Point:
2227
def __ne__(self, other: object) -> bool:
2328
"""__ne__(self: object, other: object) -> bool"""
2429
@property
25-
def name(self) -> str: ...
30+
def name(self) -> str:
31+
"""name(self: handle) -> str
32+
33+
name(self: handle) -> str
34+
"""
2635
@property
27-
def value(self) -> int: ...
36+
def value(self) -> int:
37+
"""(arg0: pybind11_fixtures.demo.Point.AngleUnit) -> int"""
2838

2939
class LengthUnit:
40+
"""Members:
41+
42+
mm
43+
44+
pixel
45+
46+
inch"""
3047
__members__: ClassVar[dict] = ... # read-only
3148
__entries: ClassVar[dict] = ...
3249
inch: ClassVar[Point.LengthUnit] = ...
@@ -45,9 +62,14 @@ class Point:
4562
def __ne__(self, other: object) -> bool:
4663
"""__ne__(self: object, other: object) -> bool"""
4764
@property
48-
def name(self) -> str: ...
65+
def name(self) -> str:
66+
"""name(self: handle) -> str
67+
68+
name(self: handle) -> str
69+
"""
4970
@property
50-
def value(self) -> int: ...
71+
def value(self) -> int:
72+
"""(arg0: pybind11_fixtures.demo.Point.LengthUnit) -> int"""
5173
angle_unit: ClassVar[Point.AngleUnit] = ...
5274
length_unit: ClassVar[Point.LengthUnit] = ...
5375
x_axis: ClassVar[Point] = ... # read-only
@@ -94,7 +116,8 @@ class Point:
94116
2. distance_to(self: pybind11_fixtures.demo.Point, other: pybind11_fixtures.demo.Point) -> float
95117
"""
96118
@property
97-
def length(self) -> float: ...
119+
def length(self) -> float:
120+
"""(arg0: pybind11_fixtures.demo.Point) -> float"""
98121

99122
def answer() -> int:
100123
'''answer() -> int

0 commit comments

Comments
 (0)