From 9f1700830f953742f1170d51c35bfe974a49ac42 Mon Sep 17 00:00:00 2001 From: Brian Pugh Date: Mon, 23 Feb 2026 11:23:08 -0500 Subject: [PATCH 1/2] specify pydoctor for >= python 3.9; only needed for docs building. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c2a019d..88d846e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,7 +40,7 @@ test = [ "pytest", ] docs = [ - "pydoctor >= 25.4.0", + "pydoctor >= 25.4.0; python_version>='3.9'", ] dev = [ "docstring_parser[test]", From 869ba5a590c89a7da4e88b877b60b185d889a6d3 Mon Sep 17 00:00:00 2001 From: Brian Pugh Date: Mon, 23 Feb 2026 14:40:41 -0500 Subject: [PATCH 2/2] google: add Notes, Warnings, See Also, References, Deprecated sections --- docstring_parser/google.py | 49 +++++++- docstring_parser/tests/test_google.py | 162 ++++++++++++++++++++++++++ 2 files changed, 210 insertions(+), 1 deletion(-) diff --git a/docstring_parser/google.py b/docstring_parser/google.py index 244e698..19ebe2e 100644 --- a/docstring_parser/google.py +++ b/docstring_parser/google.py @@ -7,12 +7,14 @@ from enum import IntEnum from .common import ( + DEPRECATION_KEYWORDS, EXAMPLES_KEYWORDS, PARAM_KEYWORDS, RAISES_KEYWORDS, RETURNS_KEYWORDS, YIELDS_KEYWORDS, Docstring, + DocstringDeprecated, DocstringExample, DocstringMeta, DocstringParam, @@ -58,6 +60,15 @@ class Section(namedtuple("SectionBase", "title key type")): Section("Examples", "examples", SectionType.SINGULAR), Section("Returns", "returns", SectionType.SINGULAR_OR_MULTIPLE), Section("Yields", "yields", SectionType.SINGULAR_OR_MULTIPLE), + Section("Notes", "notes", SectionType.SINGULAR), + Section("Note", "notes", SectionType.SINGULAR), + Section("Warnings", "warnings", SectionType.SINGULAR), + Section("Warning", "warnings", SectionType.SINGULAR), + Section("See Also", "see_also", SectionType.SINGULAR), + Section("Related", "see_also", SectionType.SINGULAR), + Section("References", "references", SectionType.SINGULAR), + Section("Reference", "references", SectionType.SINGULAR), + Section("Deprecated", "deprecation", SectionType.SINGULAR), ] @@ -145,6 +156,23 @@ def _build_single_meta(section: Section, desc: str) -> DocstringMeta: return DocstringExample( args=[section.key], snippet=None, description=desc ) + if section.key in DEPRECATION_KEYWORDS: + version, desc, *_ = ( + desc.split(sep="\n", maxsplit=1) + [None, None] + ) + if desc is not None: + desc = desc.strip() + if not desc: + desc = None + if version is not None: + version = version.strip() + if not version: + version = None + return DocstringDeprecated( + args=[section.key], + description=desc, + version=version, + ) if section.key in PARAM_KEYWORDS: raise ParseError("Expected paramenter name.") return DocstringMeta(args=[section.key], description=desc) @@ -397,9 +425,28 @@ def process_sect(name: str, args: T.List[T.Any]): parts.append("-" * len(parts[-1])) process_one(ret) + if docstring.deprecation: + dep = docstring.deprecation + parts.append("Deprecated:") + dep_lines = [] + if dep.version: + dep_lines.append(dep.version) + if dep.description: + dep_lines.extend(dep.description.splitlines()) + if dep_lines: + lines = [indent + l for l in dep_lines] + parts.append("\n".join(lines)) + parts.append("") + for meta in docstring.meta: if isinstance( - meta, (DocstringParam, DocstringReturns, DocstringRaises) + meta, + ( + DocstringDeprecated, + DocstringParam, + DocstringReturns, + DocstringRaises, + ), ): continue # Already handled parts.append(meta.args[0].replace("_", "").title() + ":") diff --git a/docstring_parser/tests/test_google.py b/docstring_parser/tests/test_google.py index 5a3570a..aaeef82 100644 --- a/docstring_parser/tests/test_google.py +++ b/docstring_parser/tests/test_google.py @@ -1001,3 +1001,165 @@ def test_compose_expanded(source: str, expected: str) -> None: compose(parse(source), rendering_style=RenderingStyle.EXPANDED) == expected ) + + +def test_notes_section() -> None: + """Test parsing a Notes section.""" + docstring = parse( + """ + Short description + + Notes: + This is important. + Second line. + """ + ) + assert docstring.short_description == "Short description" + assert len(docstring.meta) == 1 + assert docstring.meta[0].args == ["notes"] + assert docstring.meta[0].description == "This is important.\nSecond line." + + +def test_warnings_section() -> None: + """Test parsing a Warnings section.""" + docstring = parse( + """ + Short description + + Warnings: + Be careful with this. + """ + ) + assert docstring.short_description == "Short description" + assert len(docstring.meta) == 1 + assert docstring.meta[0].args == ["warnings"] + assert docstring.meta[0].description == "Be careful with this." + + +def test_see_also_section() -> None: + """Test parsing a See Also section.""" + docstring = parse( + """ + Short description + + See Also: + bar: related function. + """ + ) + assert docstring.short_description == "Short description" + assert len(docstring.meta) == 1 + assert docstring.meta[0].args == ["see_also"] + assert docstring.meta[0].description == "bar: related function." + + +def test_references_section() -> None: + """Test parsing a References section.""" + docstring = parse( + """ + Short description + + References: + Some paper (2024). + """ + ) + assert docstring.short_description == "Short description" + assert len(docstring.meta) == 1 + assert docstring.meta[0].args == ["references"] + assert docstring.meta[0].description == "Some paper (2024)." + + +def test_deprecated_section() -> None: + """Test parsing a Deprecated section with version.""" + docstring = parse( + """ + Short description + + Deprecated: + 1.6.0 + Use new_function instead. + """ + ) + assert docstring.short_description == "Short description" + assert docstring.deprecation is not None + assert docstring.deprecation.version == "1.6.0" + assert docstring.deprecation.description == "Use new_function instead." + + +def test_deprecated_section_no_version() -> None: + """Test parsing a Deprecated section without version.""" + docstring = parse( + """ + Short description + + Deprecated: + Use new_function instead. + """ + ) + assert docstring.short_description == "Short description" + assert docstring.deprecation is not None + assert docstring.deprecation.version == "Use new_function instead." + assert docstring.deprecation.description is None + + +def test_notes_with_other_sections() -> None: + """Test Notes coexists with Args and Returns.""" + docstring = parse( + """ + Short description + + Args: + arg1 (int): first arg + + Returns: + str: a value + + Notes: + This is important. + """ + ) + assert docstring.short_description == "Short description" + assert len(docstring.params) == 1 + assert docstring.params[0].arg_name == "arg1" + assert docstring.returns is not None + assert docstring.returns.type_name == "str" + notes = [m for m in docstring.meta if m.args == ["notes"]] + assert len(notes) == 1 + assert notes[0].description == "This is important." + + +@pytest.mark.parametrize( + "source, expected_key", + [ + ("Note:\n a note", "notes"), + ("Notes:\n a note", "notes"), + ("Warning:\n be careful", "warnings"), + ("Warnings:\n be careful", "warnings"), + ("Reference:\n some ref", "references"), + ("References:\n some ref", "references"), + ("Related:\n see bar", "see_also"), + ("See Also:\n see bar", "see_also"), + ], +) +def test_singular_aliases(source: str, expected_key: str) -> None: + """Test that singular/plural aliases map to the same key.""" + docstring = parse(f"Short description\n\n{source}") + assert len(docstring.meta) == 1 + assert docstring.meta[0].args == [expected_key] + + +def test_notes_compose_round_trip() -> None: + """Test that a Notes section survives a parse-compose-parse cycle.""" + original = """Short description + +Notes: + This is important. + Second line.""" + docstring1 = parse(original) + composed = compose(docstring1) + docstring2 = parse(composed) + + notes1 = [m for m in docstring1.meta if m.args == ["notes"]] + notes2 = [m for m in docstring2.meta if m.args == ["notes"]] + assert len(notes1) == 1 + assert len(notes2) == 1 + assert notes1[0].description == notes2[0].description