From b0620f86e1767183d776771992ce12f961efe395 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Fri, 1 Sep 2023 17:35:10 +0200 Subject: [PATCH] feat: Add option to make parentheses around the type of returned values optional (Google-style) Issue #137: https://github.com/mkdocstrings/griffe/issues/137 --- docs/docstrings.md | 91 ++++++++++++++++++++-------- src/griffe/docstrings/google.py | 28 ++++++--- tests/test_docstrings/test_google.py | 37 +++++++++++ 3 files changed, 123 insertions(+), 33 deletions(-) diff --git a/docs/docstrings.md b/docs/docstrings.md index b19215a8..1e620e52 100644 --- a/docs/docstrings.md +++ b/docs/docstrings.md @@ -25,18 +25,6 @@ the text as if it is Markdown, or AsciiDoc, or reStructuredText, etc.. ### Google-style -The parser accepts a few options: - -- `ignore_init_summary`: Ignore the first line in `__init__` methods' docstrings. - Useful when merging `__init__` docstring into class' docstrings - with mkdocstrings-python's [`merge_init_into_class`][merge_init] option. Default: false. -- `trim_doctest_flags`: Remove the [doctest flags][] written as comments in `pycon` snippets within a docstring. - These flags are used to alter the behavior of [doctest][] when testing docstrings, - and should not be visible in your docs. Default: true. -- `warn_unknown_params`: Warn about parameters documented in docstrings that do not appear in the signature. Default: true. -- `returns_multiple_items`: Parse Returns sections as if they contain multiple items. - It means that continuation lines must be indented. Default: true. - Sections are written like this: ``` @@ -129,6 +117,24 @@ def foo(a, b): """ ``` +#### Parser options + +The parser accepts a few options: + +- `ignore_init_summary`: Ignore the first line in `__init__` methods' docstrings. + Useful when merging `__init__` docstring into class' docstrings + with mkdocstrings-python's [`merge_init_into_class`][merge_init] option. Default: false. +- `trim_doctest_flags`: Remove the [doctest flags][] written as comments in `pycon` snippets within a docstring. + These flags are used to alter the behavior of [doctest][] when testing docstrings, + and should not be visible in your docs. Default: true. +- `warn_unknown_params`: Warn about parameters documented in docstrings that do not appear in the signature. Default: true. +- `returns_multiple_items`: Parse [Returns sections](#returns) as if they contain multiple items. + It means that continuation lines must be indented. Default: true. +- `returns_named_value`: Whether to parse `thing: Description` in [Returns sections](#returns) as a name and description, + rather than a type and description. When true, type must be wrapped in parentheses: `(int): Description.`. + When false, parentheses are optional but the items cannot be named: `int: Description`. + + #### Attributes - Multiple items allowed @@ -688,6 +694,22 @@ def foo() -> tuple[bool, float]: ... ``` +You have to indent each continuation line when documenting returned values, +even if there's only one value returned: + +```python +"""Foo. + +Returns: + success: Whether it succeeded. + A longer description of what is considered success, + and what is considered failure. +""" +``` + +If you don't want to indent continuation lines for the only returned value, +use the [`returns_multiple_items=False`](#parser-options) parser option. + Type annotations can as usual be overridden using types in parentheses in the docstring itself: @@ -700,6 +722,21 @@ Returns: """ ``` +If you want to specify the type without a name, you still have to wrap the type in parentheses: + +```python +"""Foo. + +Returns: + (int): Whether it succeeded. + (Decimal): Final precision. +""" +``` + +If you don't want to wrap the type in parentheses, +use the [`returns_named_value=False`](#parser-options) parser option. +Setting it to false will disallow specifying a name. + TIP: **Types in docstrings are resolved using the docstrings' function scope.** See previous tips for types in docstrings. @@ -711,20 +748,6 @@ multiple items. ### Numpydoc-style -The parser accepts a few options: - -- `ignore_init_summary`: Ignore the first line in `__init__` methods' docstrings. - Useful when merging `__init__` docstring into class' docstrings - with mkdocstrings-python's [`merge_init_into_class`][merge_init] option. Default: false. -- `trim_doctest_flags`: Remove the [doctest flags][] written as comments in `pycon` snippets within a docstring. - These flags are used to alter the behavior of [doctest][] when testing docstrings, - and should not be visible in your docs. Default: true. -- `warn_unknown_params`: Warn about parameters documented in docstrings that do not appear in the signature. Default: true. -- `allow_section_blank_line`: Allow blank lines in sections' content. - When false, a blank line finishes the current section. - When true, single blank lines are kept as part of the section. - You can terminate sections with double blank lines. Default: false. - Sections are written like this: ``` @@ -805,6 +828,22 @@ several syntaxes are supported: """ ``` +#### Parser options + +The parser accepts a few options: + +- `ignore_init_summary`: Ignore the first line in `__init__` methods' docstrings. + Useful when merging `__init__` docstring into class' docstrings + with mkdocstrings-python's [`merge_init_into_class`][merge_init] option. Default: false. +- `trim_doctest_flags`: Remove the [doctest flags][] written as comments in `pycon` snippets within a docstring. + These flags are used to alter the behavior of [doctest][] when testing docstrings, + and should not be visible in your docs. Default: true. +- `warn_unknown_params`: Warn about parameters documented in docstrings that do not appear in the signature. Default: true. +- `allow_section_blank_line`: Allow blank lines in sections' content. + When false, a blank line finishes the current section. + When true, single blank lines are kept as part of the section. + You can terminate sections with double blank lines. Default: false. + #### Attributes - Multiple items allowed diff --git a/src/griffe/docstrings/google.py b/src/griffe/docstrings/google.py index 7ca55e18..9074f785 100644 --- a/src/griffe/docstrings/google.py +++ b/src/griffe/docstrings/google.py @@ -428,7 +428,8 @@ def _read_returns_section( docstring: Docstring, *, offset: int, - returns_multiple_items: bool, + returns_multiple_items: bool = True, + returns_named_value: bool = True, **options: Any, ) -> tuple[DocstringSectionReturns | None, int]: returns = [] @@ -440,12 +441,20 @@ def _read_returns_section( block = [(new_offset, one_block.splitlines())] for index, (line_number, return_lines) in enumerate(block): - match = _RE_NAME_ANNOTATION_DESCRIPTION.match(return_lines[0]) - if not match: - _warn(docstring, line_number, f"Failed to get name, annotation or description from '{return_lines[0]}'") - continue - - name, annotation, description = match.groups() + if returns_named_value: + match = _RE_NAME_ANNOTATION_DESCRIPTION.match(return_lines[0]) + if not match: + _warn(docstring, line_number, f"Failed to get name, annotation or description from '{return_lines[0]}'") + continue + name, annotation, description = match.groups() + else: + name = None + if ":" in return_lines[0]: + annotation, description = return_lines[0].split(":", 1) + annotation = annotation.lstrip("(").rstrip(")") + else: + annotation = None + description = return_lines[0] description = "\n".join([description.lstrip(), *return_lines[1:]]).rstrip("\n") if annotation: @@ -689,6 +698,7 @@ def parse( trim_doctest_flags: bool = True, returns_multiple_items: bool = True, warn_unknown_params: bool = True, + returns_named_value: bool = True, **options: Any, ) -> list[DocstringSection]: """Parse a docstring. @@ -702,6 +712,9 @@ def parse( trim_doctest_flags: Whether to remove doctest flags from Python example blocks. returns_multiple_items: Whether the `Returns` section has multiple items. warn_unknown_params: Warn about documented parameters not appearing in the signature. + returns_named_value: Whether to parse `thing: Description` in returns sections as a name and description, + rather than a type and description. When true, type must be wrapped in parentheses: `(int): Description.`. + When false, parentheses are optional but the items cannot be named: `int: Description`. **options: Additional parsing options. Returns: @@ -718,6 +731,7 @@ def parse( "trim_doctest_flags": trim_doctest_flags, "returns_multiple_items": returns_multiple_items, "warn_unknown_params": warn_unknown_params, + "returns_named_value": returns_named_value, **options, } diff --git a/tests/test_docstrings/test_google.py b/tests/test_docstrings/test_google.py index 82b5e4ba..3f761892 100644 --- a/tests/test_docstrings/test_google.py +++ b/tests/test_docstrings/test_google.py @@ -1440,3 +1440,40 @@ def test_avoid_false_positive_sections(parse_google: ParserType) -> None: "Possible section skipped, reasons: Missing blank line above section; Extraneous blank line below section title", "Possible admonition skipped, reasons: Missing blank line above admonition; Extraneous blank line below admonition title", ] + + +def test_type_in_returns_without_parentheses(parse_google: ParserType) -> None: + """Assert we can parse the return type without parentheses. + + Parameters: + parse_google: Fixture parser. + """ + docstring = """ + Summary. + + Returns: + int: Description + on several lines. + """ + sections, warnings = parse_google(docstring, returns_named_value=False) + assert len(sections) == 2 + assert not warnings + retval = sections[1].value[0] + assert retval.name == "" + assert retval.annotation == "int" + assert retval.description == "Description\non several lines." + + docstring = """ + Summary. + + Returns: + Description + on several lines. + """ + sections, warnings = parse_google(docstring, returns_named_value=False) + assert len(sections) == 2 + assert len(warnings) == 1 + retval = sections[1].value[0] + assert retval.name == "" + assert retval.annotation is None + assert retval.description == "Description\non several lines."