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
11 changes: 10 additions & 1 deletion great_docs/_renderer/_render/docclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,16 @@ def attribute_member_pages(self) -> list[layout.MemberPage]:
def docstring_sections_content(self):
items = super().docstring_sections_content
titles = set(item[0] for item in items)
if not self.is_dataclass or "Parameters" in titles or not len(self.parameters):
# When the author documents the fields themselves (via either a
# `Parameters` or an `Attributes` docstring section), don't also
# synthesize a "Parameter Attributes" section — that would duplicate the
# same fields. Which of the two sections to use is left to the docs writer.
if (
not self.is_dataclass
or "Parameters" in titles
or "Attributes" in titles
or not len(self.parameters)
):
return items

# Create and insert Parameter Attributes
Expand Down
20 changes: 13 additions & 7 deletions test-packages/synthetic/specs/gdtest_dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@
gdtest_dataclasses — @dataclass objects.

Dimensions: A1, B1, C5, D1, E6, F6, G1, H7
Focus: 3 dataclasses: one with default_factory, one frozen, one that also
defines methods. Tests dataclass field documentation, __init__
generation, and that a dataclass which defines methods still renders its
constructor signature (regression: `Class.overloads` is a dict keyed by
member name, so any class with methods was rendered through the overload
path and lost its constructor parameters).
Focus: 3 dataclasses: one with default_factory, one frozen (documented with an
`Attributes` section), one that also defines methods. Tests dataclass
field documentation, __init__ generation, that fields documented under
either a `Parameters` or an `Attributes` section render once (no
duplicated "Parameter Attributes"), and that a dataclass which defines
methods still renders its constructor signature (regression:
`Class.overloads` is a dict keyed by member name, so any class with
methods was rendered through the overload path and lost its constructor
parameters).
"""

SPEC = {
Expand Down Expand Up @@ -62,7 +65,10 @@ class Record:
"""
An immutable data record.

Parameters
Documented with an ``Attributes`` section (rather than
``Parameters``) to exercise that rendering path.

Attributes
----------
id
Unique record identifier.
Expand Down
63 changes: 63 additions & 0 deletions tests/renderer/test_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,66 @@ class Widget:
qmd = render_code_variable(code, "Widget")

assert "Widget(name, size=1)" in qmd


def test_dataclass_attributes_section_not_duplicated():
"""A dataclass documented with an `Attributes` section renders that section
once, without a synthesized "Parameter Attributes" duplicate.

The author may document a dataclass's fields with either a `Parameters` or
an `Attributes` section; great-docs must not also auto-generate a
"Parameter Attributes" section listing the same fields.
"""
code = '''
from dataclasses import dataclass

@dataclass
class Widget:
"""A widget.

Attributes
----------
name
The widget name.
size
The widget size.
"""

name: str
size: int = 1
'''
qmd = render_code_variable(code, "Widget")

assert "## Attributes {.doc-attributes}" in qmd
assert "## Parameter Attributes {.doc-parameter-attributes}" not in qmd
# The author's descriptions must be preserved.
assert "The widget name." in qmd


def test_dataclass_parameters_section_not_duplicated():
"""A dataclass documented with a `Parameters` section renders that section
once, without a synthesized "Parameter Attributes" duplicate.
"""
code = '''
from dataclasses import dataclass

@dataclass
class Widget:
"""A widget.

Parameters
----------
name
The widget name.
size
The widget size.
"""

name: str
size: int = 1
'''
qmd = render_code_variable(code, "Widget")

assert "## Parameters {.doc-parameters}" in qmd
assert "## Parameter Attributes {.doc-parameter-attributes}" not in qmd
assert "The widget name." in qmd
31 changes: 31 additions & 0 deletions tests/test_gdg_rendered.py
Original file line number Diff line number Diff line change
Expand Up @@ -974,6 +974,37 @@ def test_dataclass_with_methods_signature_has_fields():
)


@pytest.mark.dedicated
@requires_bs4
def test_dataclass_attributes_section_not_duplicated():
"""A dataclass documented with an `Attributes` section renders it once,
without a synthesized "Parameter Attributes" duplicate.

Fields may be documented with either a `Parameters` or an `Attributes`
section; great-docs must not also auto-generate a "Parameter Attributes"
section listing the same fields. (`Record` in this package uses an
`Attributes` section.)
"""
pkg = "gdtest_dataclasses"
if not _has_rendered_site(pkg):
pytest.skip("gdtest_dataclasses not rendered")

ref = _ref_dir(pkg)
page = ref / "Record.html"
if not page.exists():
pytest.skip("Record.html not found")

soup = _load_html(page)

assert soup.select_one("section.doc-attributes") is not None, (
"Record.html: expected an Attributes section"
)
assert soup.select_one("section.doc-parameter-attributes") is None, (
"Record.html: 'Attributes'-documented dataclass should not also render a "
"synthesized 'Parameter Attributes' section"
)


@pytest.mark.dedicated
@requires_bs4
def test_async_functions_have_badge():
Expand Down
Loading