Skip to content

Commit 50b9597

Browse files
authored
fix: Always return strings (not None) and warn about missing descriptions in numpy parser
Issue #137: #137 PR #138: #138
1 parent f36a3c8 commit 50b9597

File tree

2 files changed

+79
-20
lines changed

2 files changed

+79
-20
lines changed

Diff for: src/pytkdocs/parsers/docstrings/numpy.py

+36-20
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,16 @@ def read_parameters_section(
8989
if signature_param.default is not empty:
9090
default = signature_param.default
9191
kind = signature_param.kind
92+
93+
description = param.description or ""
94+
if not description:
95+
self.error(f"No description for parameter '{name}'")
96+
9297
parameters.append(
9398
Parameter(
9499
name=param.arg_name,
95100
annotation=type_name,
96-
description=param.description,
101+
description=description,
97102
default=default,
98103
kind=kind,
99104
)
@@ -124,6 +129,9 @@ def read_attributes_section(
124129
docstring_attributes = [p for p in docstring_obj.params if p.args[0] == "attribute"]
125130

126131
for attr in docstring_attributes:
132+
description = attr.description or ""
133+
if not description:
134+
self.error(f"No description for attribute '{attr.arg_name}'")
127135
attributes.append(
128136
Attribute(
129137
name=attr.arg_name,
@@ -157,7 +165,10 @@ def read_exceptions_section(
157165
except_obj = docstring_obj.raises
158166

159167
for exception in except_obj:
160-
exceptions.append(AnnotatedObject(exception.type_name, exception.description))
168+
description = exception.description or ""
169+
if not description:
170+
self.error(f"No description for exception '{exception.type_name}'")
171+
exceptions.append(AnnotatedObject(exception.type_name, description))
161172

162173
if exceptions:
163174
return Section(Section.Type.EXCEPTIONS, exceptions)
@@ -178,26 +189,31 @@ def read_return_section(
178189
Returns:
179190
A `Section` object (or `None` if section is empty).
180191
"""
181-
return_obj = docstring_obj.returns if docstring_obj.returns else []
182-
text = return_obj.description if return_obj else ""
183-
184-
if self.context["signature"]:
185-
annotation = self.context["signature"].return_annotation
186-
else:
187-
annotation = self.context["annotation"]
188-
189-
if annotation is empty:
190-
if text:
191-
annotation = return_obj.type_name or empty
192-
text = return_obj.description
193-
elif return_obj and annotation is empty:
192+
if docstring_obj.returns:
193+
return_obj = docstring_obj.returns
194+
195+
if return_obj.description:
196+
description = return_obj.description
197+
else:
198+
self.error("Empty return description")
199+
description = ""
200+
201+
if self.context["signature"]:
202+
annotation = self.context["signature"].return_annotation
203+
else:
204+
annotation = self.context["annotation"]
205+
206+
if annotation is empty and return_obj.type_name:
207+
annotation = return_obj.type_name
208+
209+
if not annotation:
194210
self.error("No return type annotation")
211+
annotation = ""
195212

196-
if return_obj and not text:
197-
self.error("Empty return description")
198-
if not return_obj or annotation is empty or not text:
199-
return None
200-
return Section(Section.Type.RETURN, AnnotatedObject(annotation, text))
213+
if annotation or description:
214+
return Section(Section.Type.RETURN, AnnotatedObject(annotation, description))
215+
216+
return None
201217

202218
def read_examples_section(
203219
self,

Diff for: tests/test_parsers/test_docstrings/test_numpy.py

+43
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from textwrap import dedent
55

66
from pytkdocs.loader import Loader
7+
from pytkdocs.parsers.docstrings.base import Section
78
from pytkdocs.parsers.docstrings.numpy import Numpy
89

910

@@ -81,6 +82,48 @@ def test_sections_without_signature():
8182
assert "param" in error
8283

8384

85+
def test_sections_without_description():
86+
"""Parse a docstring without descriptions."""
87+
# type of return value always required
88+
sections, errors = parse(
89+
"""
90+
Sections without descriptions.
91+
92+
Parameters
93+
----------
94+
void : str
95+
niet : str
96+
97+
Raises
98+
------
99+
GlobalError
100+
101+
Returns
102+
-------
103+
bool
104+
"""
105+
)
106+
107+
# Assert that errors are as expected
108+
assert len(sections) == 4
109+
assert len(errors) == 6
110+
for error in errors[:4]:
111+
assert "param" in error
112+
assert "exception" in errors[4]
113+
assert "return description" in errors[5]
114+
115+
# Assert that no descriptions are ever None (can cause exceptions downstream)
116+
assert sections[1].type is Section.Type.PARAMETERS
117+
for p in sections[1].value:
118+
assert p.description is not None
119+
120+
assert sections[2].type is Section.Type.EXCEPTIONS
121+
for p in sections[2].value:
122+
assert p.description is not None
123+
124+
assert sections[3].type is Section.Type.RETURN
125+
assert sections[3].value.description is not None
126+
84127
def test_property_docstring():
85128
"""Parse a property docstring."""
86129
class_ = Loader().get_object_documentation("tests.fixtures.parsing.docstrings.NotDefinedYet")

0 commit comments

Comments
 (0)