Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
- General: **Breaking:** Remove `DocstringMeta.args`, which each style filled differently; the new `key`, `name` and `kind` fields say the same, and the meta classes no longer take `args`
- General: **Breaking:** Read the keyword sets in `common`, such as `PARAM_KEYWORDS`, once on import; pass `sections` instead of changing them
- General: **Breaking:** Make `DocstringStyle` values the style names, such as `"google"`, instead of numbers
- General: **Breaking:** Leave attributes out of `Docstring.params`, and list them in the new `Docstring.attrs`
- Google, Numpydoc: **Breaking:** Remove customising parsing by overriding internals such as `titles_re`, `title_pattern`, `_build_meta` and `KV_REGEX`; use `SectionSpec` instead
- General: Parse docstrings with `\r\n` or `\r` line endings the same as with `\n`, on every Python version
- General: Dedent docstrings the same way on every Python version; from 3.13, `inspect.cleandoc` dedented nothing after a blank line holding a tab or form feed
- General: Strip the short description, which kept leading spaces when a later line was indented less than it
- General: Quote at most the first 60 characters of the text a `ParseError` points at, instead of the whole chunk
- General: Parse a docstring with no fields or sections once in auto-detection instead of four times, making it about 47% faster
- General: Raise `ValueError` instead of `KeyError` from `compose()` when the docstring has no style and none is given
- General: Stop `combine_docstrings` from taking an attribute named like a param as that param
- General: Give every meta a `kind`, a `Kind` that tells what it documents, such as a keyword param or a warning
- General: Compose items into sections by their `kind`, so a meta made by hand needs `kind=` for a sub-kind such as `Kind.ATTRIBUTE`
- General: Add `docstring_parser.sections`, where a `SectionSpec` declares a section or field for any style; pass a list as `sections` to `parse()` or a parser, starting from `default_sections(style)`
Expand Down
2 changes: 1 addition & 1 deletion docstring_parser/attrdoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ 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)
params = set(p.arg_name for p in [*docstring.params, *docstring.attrs])
for arg_name, (description, type_name, default) in (
AttributeDocstrings().get_attr_docs(obj).items()
):
Expand Down
19 changes: 17 additions & 2 deletions docstring_parser/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,8 +306,23 @@ def description(self) -> T.Optional[str]:

@property
def params(self) -> T.List[DocstringParam]:
"""Return a list of information on function params."""
return [item for item in self.meta if isinstance(item, DocstringParam)]
"""Return a list of information on function params, without the
attributes."""
return [
item
for item in self.meta
if isinstance(item, DocstringParam)
and item.kind is not Kind.ATTRIBUTE
]

@property
def attrs(self) -> T.List[DocstringParam]:
"""Return a list of information on attributes."""
return [
item
for item in self.meta
if isinstance(item, DocstringParam) and item.kind is Kind.ATTRIBUTE
]

@property
def raises(self) -> T.List[DocstringRaises]:
Expand Down
24 changes: 24 additions & 0 deletions docstring_parser/tests/test_kinds.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,27 @@ def test_kind_must_fit_class() -> None:
def test_sub_kinds(style: DocstringStyle, text: str, kinds: list) -> None:
"""Test that each style tells the kinds of params and raises apart."""
assert [m.kind for m in parse(text, style).meta] == kinds


@pytest.mark.parametrize(
("style", "text"),
[
(DocstringStyle.REST, ":param x: X.\n:attribute y: Y."),
(DocstringStyle.EPYDOC, "@param x: X.\n@ivar y: Y."),
(
DocstringStyle.GOOGLE,
"S.\n\nArgs:\n x: X.\n\nAttributes:\n y: Y.",
),
(
DocstringStyle.NUMPYDOC,
"S.\n\nParameters\n----------\nx\n X.\n\n"
"Attributes\n----------\ny\n Y.",
),
],
ids=lambda v: v.name.lower() if isinstance(v, DocstringStyle) else "",
)
def test_params_leave_out_attributes(style: DocstringStyle, text: str) -> None:
"""Test that params and attrs each list their own items."""
docstring = parse(text, style)
assert [p.arg_name for p in docstring.params] == ["x"]
assert [a.arg_name for a in docstring.attrs] == ["y"]
34 changes: 19 additions & 15 deletions docstring_parser/tests/test_parse_from_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ def test_from_module_attribute_docstrings() -> None:

assert docstring.short_description is not None
assert "parse_from_object" in docstring.short_description
assert len(docstring.params) == 1
assert docstring.params[0].arg_name == "module_attr"
assert docstring.params[0].type_name == "int"
assert docstring.params[0].description == "Description for module_attr"
assert len(docstring.attrs) == 1
assert docstring.params == []
assert docstring.attrs[0].arg_name == "module_attr"
assert docstring.attrs[0].type_name == "int"
assert docstring.attrs[0].description == "Description for module_attr"


def test_from_class_attribute_docstrings() -> None:
Expand All @@ -40,13 +41,14 @@ class StandardCase:
assert docstring.short_description == "Short description"
assert docstring.long_description == "Long description"
assert docstring.description == "Short description\nLong description"
assert len(docstring.params) == 2
assert docstring.params[0].arg_name == "attr_one"
assert docstring.params[0].type_name == "str"
assert docstring.params[0].description == "Description for attr_one"
assert docstring.params[1].arg_name == "attr_two"
assert docstring.params[1].type_name == "bool"
assert docstring.params[1].description == "Description for attr_two"
assert len(docstring.attrs) == 2
assert docstring.params == []
assert docstring.attrs[0].arg_name == "attr_one"
assert docstring.attrs[0].type_name == "str"
assert docstring.attrs[0].description == "Description for attr_one"
assert docstring.attrs[1].arg_name == "attr_two"
assert docstring.attrs[1].type_name == "bool"
assert docstring.attrs[1].description == "Description for attr_two"


def test_from_class_attribute_docstrings_without_type() -> None:
Expand All @@ -61,10 +63,11 @@ class WithoutType:
assert docstring.short_description is None
assert docstring.long_description is None
assert docstring.description is None
assert len(docstring.params) == 1
assert docstring.params[0].arg_name == "attr_one"
assert docstring.params[0].type_name is None
assert docstring.params[0].description == "Description for attr_one"
assert len(docstring.attrs) == 1
assert docstring.params == []
assert docstring.attrs[0].arg_name == "attr_one"
assert docstring.attrs[0].type_name is None
assert docstring.attrs[0].description == "Description for attr_one"


def test_from_class_without_source() -> None:
Expand All @@ -85,6 +88,7 @@ class WithoutSource:
assert docstring.long_description is None
assert docstring.description == "Short description"
assert len(docstring.params) == 0
assert len(docstring.attrs) == 0


def test_from_function() -> None:
Expand Down
Loading