Skip to content

Commit 514467c

Browse files
committed
refactor: Only add docstring sections when elements are annotated with Doc
Issue-13: #13
1 parent abee85f commit 514467c

File tree

3 files changed

+146
-40
lines changed

3 files changed

+146
-40
lines changed

src/griffe_typingdoc/_docstrings.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
)
2323

2424
if TYPE_CHECKING:
25-
from collections.abc import Iterator
25+
from collections.abc import Iterable
2626

2727
from griffe import Function, Parameter
2828

@@ -60,7 +60,7 @@ def _to_other_parameters_section(params_dict: dict[str, dict[str, Any]]) -> Docs
6060
)
6161

6262

63-
def _to_yields_section(yield_data: Iterator[dict[str, Any]]) -> DocstringSectionYields:
63+
def _to_yields_section(yield_data: Iterable[dict[str, Any]]) -> DocstringSectionYields:
6464
return DocstringSectionYields(
6565
[
6666
DocstringYield(
@@ -73,7 +73,7 @@ def _to_yields_section(yield_data: Iterator[dict[str, Any]]) -> DocstringSection
7373
)
7474

7575

76-
def _to_receives_section(receive_data: Iterator[dict[str, Any]]) -> DocstringSectionReceives:
76+
def _to_receives_section(receive_data: Iterable[dict[str, Any]]) -> DocstringSectionReceives:
7777
return DocstringSectionReceives(
7878
[
7979
DocstringReceive(
@@ -86,7 +86,7 @@ def _to_receives_section(receive_data: Iterator[dict[str, Any]]) -> DocstringSec
8686
)
8787

8888

89-
def _to_returns_section(return_data: Iterator[dict[str, Any]]) -> DocstringSectionReturns:
89+
def _to_returns_section(return_data: Iterable[dict[str, Any]]) -> DocstringSectionReturns:
9090
return DocstringSectionReturns(
9191
[
9292
DocstringReturn(
@@ -99,7 +99,7 @@ def _to_returns_section(return_data: Iterator[dict[str, Any]]) -> DocstringSecti
9999
)
100100

101101

102-
def _to_warns_section(warn_data: Iterator[dict[str, Any]]) -> DocstringSectionWarns:
102+
def _to_warns_section(warn_data: Iterable[dict[str, Any]]) -> DocstringSectionWarns:
103103
return DocstringSectionWarns(
104104
[
105105
DocstringWarn(
@@ -111,7 +111,7 @@ def _to_warns_section(warn_data: Iterator[dict[str, Any]]) -> DocstringSectionWa
111111
)
112112

113113

114-
def _to_raises_section(raise_data: Iterator[dict[str, Any]]) -> DocstringSectionRaises:
114+
def _to_raises_section(raise_data: Iterable[dict[str, Any]]) -> DocstringSectionRaises:
115115
return DocstringSectionRaises(
116116
[
117117
DocstringRaise(

src/griffe_typingdoc/_static.py

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -103,16 +103,17 @@ def _attribute_docs(attr: Attribute, **kwargs: Any) -> str: # noqa: ARG001
103103

104104

105105
def _parameters_docs(func: Function, **kwargs: Any) -> DocstringSectionParameters | None: # noqa: ARG001
106-
params_doc: dict[str, dict[str, Any]] = defaultdict(dict)
106+
params_data: dict[str, dict[str, Any]] = defaultdict(dict)
107107
for parameter in _no_self_params(func):
108108
stars = {ParameterKind.var_positional: "*", ParameterKind.var_keyword: "**"}.get(parameter.kind, "") # type: ignore[arg-type]
109109
param_name = f"{stars}{parameter.name}"
110110
metadata = _metadata(parameter.annotation)
111-
description = f"{metadata.get('deprecated', '')} {metadata.get('doc', '')}".lstrip()
112-
params_doc[param_name]["annotation"] = parameter.annotation
113-
params_doc[param_name]["description"] = description
114-
if params_doc:
115-
return _to_parameters_section(params_doc, func)
111+
if "deprecated" in metadata or "doc" in metadata:
112+
description = f"{metadata.get('deprecated', '')} {metadata.get('doc', '')}".lstrip()
113+
params_data[param_name]["description"] = description
114+
params_data[param_name]["annotation"] = parameter.annotation
115+
if params_data:
116+
return _to_parameters_section(params_data, func)
116117
return None
117118

118119

@@ -131,20 +132,19 @@ def _other_parameters_docs(func: Function, **kwargs: Any) -> DocstringSectionPar
131132
}:
132133
slice_path = annotation.slice.canonical_path # type: ignore[union-attr]
133134
typed_dict = func.modules_collection[slice_path]
134-
params_doc = {
135-
attr.name: {"annotation": attr.annotation, "description": _metadata(attr.annotation).get("doc", "")}
135+
params_data = {
136+
attr.name: {"annotation": attr.annotation, "description": description}
136137
for attr in typed_dict.members.values()
138+
if (description := _metadata(attr.annotation).get("doc")) is not None
137139
}
138-
if params_doc:
139-
return _to_other_parameters_section(params_doc)
140+
if params_data:
141+
return _to_other_parameters_section(params_data)
140142
break
141143
return None
142144

143145

144146
def _yields_docs(func: Function, **kwargs: Any) -> DocstringSectionYields | None: # noqa: ARG001
145-
yields_section = None
146147
yield_annotation = None
147-
148148
annotation = func.returns
149149

150150
if isinstance(annotation, ExprSubscript):
@@ -158,15 +158,19 @@ def _yields_docs(func: Function, **kwargs: Any) -> DocstringSectionYields | None
158158
yield_elements = yield_annotation.slice.elements # type: ignore[union-attr]
159159
else:
160160
yield_elements = [yield_annotation]
161-
yields_section = _to_yields_section({"annotation": element, **_metadata(element)} for element in yield_elements)
161+
yield_data = [
162+
{"annotation": element, **metadata}
163+
for element in yield_elements
164+
if "doc" in (metadata := _metadata(element))
165+
]
166+
if yield_data:
167+
return _to_yields_section(yield_data)
162168

163-
return yields_section
169+
return None
164170

165171

166172
def _receives_docs(func: Function, **kwargs: Any) -> DocstringSectionReceives | None: # noqa: ARG001
167-
receives_section = None
168173
receive_annotation = None
169-
170174
annotation = func.returns
171175

172176
if isinstance(annotation, ExprSubscript) and annotation.canonical_path in {
@@ -180,17 +184,19 @@ def _receives_docs(func: Function, **kwargs: Any) -> DocstringSectionReceives |
180184
receive_elements = receive_annotation.slice.elements # type: ignore[union-attr]
181185
else:
182186
receive_elements = [receive_annotation]
183-
receives_section = _to_receives_section(
184-
{"annotation": element, **_metadata(element)} for element in receive_elements
185-
)
187+
receive_data = [
188+
{"annotation": element, **metadata}
189+
for element in receive_elements
190+
if "doc" in (metadata := _metadata(element))
191+
]
192+
if receive_data:
193+
return _to_receives_section(receive_data)
186194

187-
return receives_section
195+
return None
188196

189197

190198
def _returns_docs(func: Function, **kwargs: Any) -> DocstringSectionReturns | None: # noqa: ARG001
191-
returns_section = None
192199
return_annotation = None
193-
194200
annotation = func.returns
195201

196202
if isinstance(annotation, ExprSubscript) and annotation.canonical_path in {
@@ -209,11 +215,15 @@ def _returns_docs(func: Function, **kwargs: Any) -> DocstringSectionReturns | No
209215
return_elements = return_annotation.slice.elements # type: ignore[union-attr]
210216
else:
211217
return_elements = [return_annotation]
212-
returns_section = _to_returns_section(
213-
{"annotation": element, **_metadata(element)} for element in return_elements
214-
)
218+
return_data = [
219+
{"annotation": element, **metadata}
220+
for element in return_elements
221+
if "doc" in (metadata := _metadata(element))
222+
]
223+
if return_data:
224+
return _to_returns_section(return_data)
215225

216-
return returns_section
226+
return None
217227

218228

219229
def _warns_docs(attr_or_func: Attribute | Function, **kwargs: Any) -> DocstringSectionWarns | None: # noqa: ARG001

tests/test_extension.py

Lines changed: 104 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Tests for the Griffe extension."""
22

3+
import pytest
34
from griffe import DocstringSectionKind, Extensions, GriffeLoader, temporary_visited_package
45

56
from griffe_typingdoc import TypingDocExtension
@@ -184,21 +185,116 @@ class Options(TypedDict):
184185
extensions=Extensions(TypingDocExtension()),
185186
) as package:
186187
sections = package["A.__init__"].docstring.parsed
187-
assert len(sections) == 3
188+
assert len(sections) == 2
188189
assert sections[0].kind is DocstringSectionKind.text
189-
assert sections[1].kind is DocstringSectionKind.parameters
190-
assert sections[2].kind is DocstringSectionKind.other_parameters
191-
foo = sections[2].value[0]
190+
assert sections[1].kind is DocstringSectionKind.other_parameters
191+
foo = sections[1].value[0]
192192
assert foo.name == "foo"
193193
assert foo.description == "Foo's description."
194194
assert str(foo.annotation).startswith("Annotated[int")
195195

196196
sections = package["B.__init__"].docstring.parsed
197-
assert len(sections) == 3
197+
assert len(sections) == 2
198198
assert sections[0].kind is DocstringSectionKind.text
199-
assert sections[1].kind is DocstringSectionKind.parameters
200-
assert sections[2].kind is DocstringSectionKind.other_parameters
201-
bar = sections[2].value[0]
199+
assert sections[1].kind is DocstringSectionKind.other_parameters
200+
bar = sections[1].value[0]
202201
assert bar.name == "bar"
203202
assert bar.description == "Bar's description."
204203
assert str(bar.annotation).startswith("Annotated[str")
204+
205+
206+
@pytest.mark.parametrize(
207+
"annotation",
208+
["int", "Annotated[int, '']"],
209+
)
210+
def test_ignore_unannotated_params(annotation: str) -> None:
211+
"""Ignore parameters that are not annotated with `Doc`."""
212+
with temporary_visited_package(
213+
"package",
214+
{
215+
"__init__.py": f"{typing_imports}\ndef f(a: {annotation}):\n '''Docstring.'''",
216+
},
217+
extensions=Extensions(TypingDocExtension()),
218+
) as package:
219+
sections = package["f"].docstring.parsed
220+
assert len(sections) == 1
221+
assert sections[0].kind is DocstringSectionKind.text
222+
223+
224+
@pytest.mark.parametrize(
225+
"annotation",
226+
["int", "Annotated[int, '']"],
227+
)
228+
def test_ignore_unannotated_other_params(annotation: str) -> None:
229+
"""Ignore other parameters that are not annotated with `Doc`."""
230+
with temporary_visited_package(
231+
"package",
232+
{
233+
"__init__.py": f"""
234+
{typing_imports}
235+
from typing import TypedDict
236+
class Kwargs(TypedDict):
237+
a: {annotation}
238+
def f(**kwargs: Unpack[Kwargs]):
239+
'''Docstring.'''
240+
""",
241+
},
242+
extensions=Extensions(TypingDocExtension()),
243+
) as package:
244+
sections = package["f"].docstring.parsed
245+
assert len(sections) == 1
246+
assert sections[0].kind is DocstringSectionKind.text
247+
248+
249+
@pytest.mark.parametrize(
250+
"annotation",
251+
["int", "Annotated[int, '']"],
252+
)
253+
def test_ignore_unannotated_returns(annotation: str) -> None:
254+
"""Ignore return values that are not annotated with `Doc`."""
255+
with temporary_visited_package(
256+
"package",
257+
{
258+
"__init__.py": f"{typing_imports}\ndef f() -> {annotation}:\n '''Docstring.'''",
259+
},
260+
extensions=Extensions(TypingDocExtension()),
261+
) as package:
262+
sections = package["f"].docstring.parsed
263+
assert len(sections) == 1
264+
assert sections[0].kind is DocstringSectionKind.text
265+
266+
267+
@pytest.mark.parametrize(
268+
"annotation",
269+
["int", "Annotated[int, '']"],
270+
)
271+
def test_ignore_unannotated_yields(annotation: str) -> None:
272+
"""Ignore yields that are not annotated with `Doc`."""
273+
with temporary_visited_package(
274+
"package",
275+
{
276+
"__init__.py": f"{typing_imports}\ndef f() -> Iterator[{annotation}]:\n '''Docstring.'''",
277+
},
278+
extensions=Extensions(TypingDocExtension()),
279+
) as package:
280+
sections = package["f"].docstring.parsed
281+
assert len(sections) == 1
282+
assert sections[0].kind is DocstringSectionKind.text
283+
284+
285+
@pytest.mark.parametrize(
286+
"annotation",
287+
["int", "Annotated[int, '']"],
288+
)
289+
def test_ignore_unannotated_receives(annotation: str) -> None:
290+
"""Ignore receives that are not annotated with `Doc`."""
291+
with temporary_visited_package(
292+
"package",
293+
{
294+
"__init__.py": f"{typing_imports}\ndef f() -> Generator[int, {annotation}, None]:\n '''Docstring.'''",
295+
},
296+
extensions=Extensions(TypingDocExtension()),
297+
) as package:
298+
sections = package["f"].docstring.parsed
299+
assert len(sections) == 1
300+
assert sections[0].kind is DocstringSectionKind.text

0 commit comments

Comments
 (0)