diff --git a/CHANGELOG.md b/CHANGELOG.md index 984f0833..9c5d9c42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ - General: Give `ParseError` the `problems` of the whole docstring and the `result` parsed despite them, and mention how many more there are in its message - General: Show a docstring and its metas with the fields they have set in `repr()`, as in `DocstringParam(kind=Kind.PARAM, arg_name='x', ...)` - General: Add `raw_type_name` to params, the type as written, such as `int, optional`, before its optional mark and default are split off +- General: Keep a class attribute in `parse_from_object()` when the class docstring documents a param of the same name, instead of dropping it - General: Return the docstring of a built-in class or module from `parse_from_object()` instead of raising `TypeError`, and ignore a `__doc__` that is not a string - ReST: Stop the last field from absorbing text after the field list; that text now goes to the long description (thanks to @apoorvdarshan) - ReST: Raise `ParseError` instead of `IndexError` for a field with no name diff --git a/docstring_parser/attrdoc.py b/docstring_parser/attrdoc.py index 2f5179b0..473c28ed 100644 --- a/docstring_parser/attrdoc.py +++ b/docstring_parser/attrdoc.py @@ -111,11 +111,13 @@ def add_attribute_docstrings( :param docstring: Docstring object where found attributes are added :returns: list with names of added attributes """ - params = set(p.arg_name for p in [*docstring.params, *docstring.attrs]) + # An attribute the docstring documents already wins. A param of the + # same name, such as one ``__init__`` stores, is no attribute. + documented = set(a.arg_name for a in docstring.attrs) for arg_name, (description, type_name, default) in ( AttributeDocstrings().get_attr_docs(obj).items() ): - if arg_name not in params: + if arg_name not in documented: param = DocstringParam( key="attribute", description=description, diff --git a/docstring_parser/tests/test_parse_from_object.py b/docstring_parser/tests/test_parse_from_object.py index 22bb12ba..42b99db0 100644 --- a/docstring_parser/tests/test_parse_from_object.py +++ b/docstring_parser/tests/test_parse_from_object.py @@ -52,6 +52,46 @@ class StandardCase: assert docstring.attrs[1].description == "Description for attr_two" +def test_from_class_attribute_named_like_param() -> None: + """Test that an attribute named like a documented param is kept.""" + + class WithParam: + """Short description + + :param x: The param x. + """ + + x: int = 1 + """The attribute x.""" + + docstring = parse_from_object(WithParam) + + assert len(docstring.params) == 1 + assert docstring.params[0].arg_name == "x" + assert docstring.params[0].description == "The param x." + assert len(docstring.attrs) == 1 + assert docstring.attrs[0].arg_name == "x" + assert docstring.attrs[0].description == "The attribute x." + + +def test_from_class_documented_attribute_wins() -> None: + """Test that an attribute the docstring documents is not added twice.""" + + class WithAttribute: + """Short description + + :attribute x: From the docstring. + """ + + x: int = 1 + """From the source.""" + + docstring = parse_from_object(WithAttribute) + + assert len(docstring.attrs) == 1 + assert docstring.attrs[0].description == "From the docstring." + + def test_from_class_attribute_docstrings_without_type() -> None: """Test the parse of untyped attribute docstrings."""