From c0d3a5be317016abfd15f560319dd0c607e46a3c Mon Sep 17 00:00:00 2001 From: Joseph Richardson Date: Fri, 4 Mar 2022 15:34:52 -0500 Subject: [PATCH 1/4] fix: warn about missing descriptions and return "" not `None` --- src/pytkdocs/parsers/docstrings/numpy.py | 54 ++++++++++++------- .../test_docstrings/test_numpy.py | 31 +++++++++++ 2 files changed, 66 insertions(+), 19 deletions(-) diff --git a/src/pytkdocs/parsers/docstrings/numpy.py b/src/pytkdocs/parsers/docstrings/numpy.py index b839925..c019b6c 100644 --- a/src/pytkdocs/parsers/docstrings/numpy.py +++ b/src/pytkdocs/parsers/docstrings/numpy.py @@ -89,11 +89,16 @@ def read_parameters_section( if signature_param.default is not empty: default = signature_param.default kind = signature_param.kind + + description = param.description if param.description else "" + if not description: + self.error(f"No description for parameter '{name}'") + parameters.append( Parameter( name=param.arg_name, annotation=type_name, - description=param.description, + description=description, default=default, kind=kind, ) @@ -124,6 +129,9 @@ def read_attributes_section( docstring_attributes = [p for p in docstring_obj.params if p.args[0] == "attribute"] for attr in docstring_attributes: + description = attr.description if attr.description else "" + if not description: + self.error(f"No description for attribute '{attr.arg_name}'") attributes.append( Attribute( name=attr.arg_name, @@ -157,6 +165,9 @@ def read_exceptions_section( except_obj = docstring_obj.raises for exception in except_obj: + description = exception.description if exception.description else "" + if not description: + self.error(f"No description for exception '{exception.type_name}'") exceptions.append(AnnotatedObject(exception.type_name, exception.description)) if exceptions: @@ -178,26 +189,31 @@ def read_return_section( Returns: A `Section` object (or `None` if section is empty). """ - return_obj = docstring_obj.returns if docstring_obj.returns else [] - text = return_obj.description if return_obj else "" - - if self.context["signature"]: - annotation = self.context["signature"].return_annotation - else: - annotation = self.context["annotation"] - - if annotation is empty: - if text: - annotation = return_obj.type_name or empty - text = return_obj.description - elif return_obj and annotation is empty: + if docstring_obj.returns: + return_obj = docstring_obj.returns + + if return_obj.description: + description = return_obj.description + else: + self.error("Empty return description") + description = "" + + if self.context["signature"]: + annotation = self.context["signature"].return_annotation + else: + annotation = self.context["annotation"] + + if annotation is empty and return_obj.type_name: + annotation = return_obj.type_name + + if not annotation: self.error("No return type annotation") + annotation = "" - if return_obj and not text: - self.error("Empty return description") - if not return_obj or annotation is empty or not text: - return None - return Section(Section.Type.RETURN, AnnotatedObject(annotation, text)) + if annotation or description: + return Section(Section.Type.RETURN, AnnotatedObject(annotation, description)) + + return None def read_examples_section( self, diff --git a/tests/test_parsers/test_docstrings/test_numpy.py b/tests/test_parsers/test_docstrings/test_numpy.py index d8b2119..b254f70 100644 --- a/tests/test_parsers/test_docstrings/test_numpy.py +++ b/tests/test_parsers/test_docstrings/test_numpy.py @@ -81,6 +81,37 @@ def test_sections_without_signature(): assert "param" in error +def test_sections_without_description(): + """Parse a docstring without descriptions.""" + # type of return value always required + sections, errors = parse( + """ + Sections without descriptions. + + Parameters + ---------- + void : str + niet : str + nada : str + rien : str + + Raises + ------ + GlobalError + + Returns + ------- + bool + """ + ) + assert len(sections) == 4 + assert len(errors) == 10 + for error in errors[:8]: + assert "param" in error + assert "exception" in errors[8] + assert "return description" in errors[9] + + def test_property_docstring(): """Parse a property docstring.""" class_ = Loader().get_object_documentation("tests.fixtures.parsing.docstrings.NotDefinedYet") From bea7509eb3bde79208af72d8f6cd82f261af7eab Mon Sep 17 00:00:00 2001 From: Joseph Richardson Date: Mon, 7 Mar 2022 10:03:22 -0500 Subject: [PATCH 2/4] refactor: reduced verbosity with `or` --- src/pytkdocs/parsers/docstrings/numpy.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pytkdocs/parsers/docstrings/numpy.py b/src/pytkdocs/parsers/docstrings/numpy.py index c019b6c..3e87492 100644 --- a/src/pytkdocs/parsers/docstrings/numpy.py +++ b/src/pytkdocs/parsers/docstrings/numpy.py @@ -90,7 +90,7 @@ def read_parameters_section( default = signature_param.default kind = signature_param.kind - description = param.description if param.description else "" + description = param.description or "" if not description: self.error(f"No description for parameter '{name}'") @@ -129,7 +129,7 @@ def read_attributes_section( docstring_attributes = [p for p in docstring_obj.params if p.args[0] == "attribute"] for attr in docstring_attributes: - description = attr.description if attr.description else "" + description = attr.description or "" if not description: self.error(f"No description for attribute '{attr.arg_name}'") attributes.append( @@ -165,7 +165,7 @@ def read_exceptions_section( except_obj = docstring_obj.raises for exception in except_obj: - description = exception.description if exception.description else "" + description = exception.description or "" if not description: self.error(f"No description for exception '{exception.type_name}'") exceptions.append(AnnotatedObject(exception.type_name, exception.description)) From 02a46ce9bda11d2f0c7ad279c226ae8a9a828b00 Mon Sep 17 00:00:00 2001 From: Joseph Richardson Date: Mon, 7 Mar 2022 10:12:36 -0500 Subject: [PATCH 3/4] test: Added test 'descriptions never None' --- .../test_parsers/test_docstrings/test_numpy.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/tests/test_parsers/test_docstrings/test_numpy.py b/tests/test_parsers/test_docstrings/test_numpy.py index b254f70..2a8c0cf 100644 --- a/tests/test_parsers/test_docstrings/test_numpy.py +++ b/tests/test_parsers/test_docstrings/test_numpy.py @@ -92,8 +92,6 @@ def test_sections_without_description(): ---------- void : str niet : str - nada : str - rien : str Raises ------ @@ -104,12 +102,19 @@ def test_sections_without_description(): bool """ ) + + # Assert that errors are as expected assert len(sections) == 4 - assert len(errors) == 10 - for error in errors[:8]: + assert len(errors) == 6 + for error in errors[:4]: assert "param" in error - assert "exception" in errors[8] - assert "return description" in errors[9] + assert "exception" in errors[4] + assert "return description" in errors[5] + + # Assert that no descriptions are ever None (can cause exceptions downstream) + for s in sections: + for v in s.value: + assert v.description is not None def test_property_docstring(): From 27cda4e4f80f71a6faae040e19fc3861fd786c87 Mon Sep 17 00:00:00 2001 From: Joseph Richardson Date: Mon, 7 Mar 2022 10:21:53 -0500 Subject: [PATCH 4/4] fix: exception descriptions now never None --- src/pytkdocs/parsers/docstrings/numpy.py | 2 +- tests/test_parsers/test_docstrings/test_numpy.py | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/pytkdocs/parsers/docstrings/numpy.py b/src/pytkdocs/parsers/docstrings/numpy.py index 3e87492..8f16450 100644 --- a/src/pytkdocs/parsers/docstrings/numpy.py +++ b/src/pytkdocs/parsers/docstrings/numpy.py @@ -168,7 +168,7 @@ def read_exceptions_section( description = exception.description or "" if not description: self.error(f"No description for exception '{exception.type_name}'") - exceptions.append(AnnotatedObject(exception.type_name, exception.description)) + exceptions.append(AnnotatedObject(exception.type_name, description)) if exceptions: return Section(Section.Type.EXCEPTIONS, exceptions) diff --git a/tests/test_parsers/test_docstrings/test_numpy.py b/tests/test_parsers/test_docstrings/test_numpy.py index 2a8c0cf..c5a87c0 100644 --- a/tests/test_parsers/test_docstrings/test_numpy.py +++ b/tests/test_parsers/test_docstrings/test_numpy.py @@ -4,6 +4,7 @@ from textwrap import dedent from pytkdocs.loader import Loader +from pytkdocs.parsers.docstrings.base import Section from pytkdocs.parsers.docstrings.numpy import Numpy @@ -112,10 +113,16 @@ def test_sections_without_description(): assert "return description" in errors[5] # Assert that no descriptions are ever None (can cause exceptions downstream) - for s in sections: - for v in s.value: - assert v.description is not None - + assert sections[1].type is Section.Type.PARAMETERS + for p in sections[1].value: + assert p.description is not None + + assert sections[2].type is Section.Type.EXCEPTIONS + for p in sections[2].value: + assert p.description is not None + + assert sections[3].type is Section.Type.RETURN + assert sections[3].value.description is not None def test_property_docstring(): """Parse a property docstring."""