Skip to content

Commit 02a2d01

Browse files
authored
Merge pull request #241 from posit-dev/fix-dataclass-section-duplication
fix: let dataclass fields be documented with either Attributes or Parameters, without duplication
2 parents 7d99fe9 + 378299a commit 02a2d01

4 files changed

Lines changed: 117 additions & 8 deletions

File tree

great_docs/_renderer/_render/docclass.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,16 @@ def attribute_member_pages(self) -> list[layout.MemberPage]:
7171
def docstring_sections_content(self):
7272
items = super().docstring_sections_content
7373
titles = set(item[0] for item in items)
74-
if not self.is_dataclass or "Parameters" in titles or not len(self.parameters):
74+
# When the author documents the fields themselves (via either a
75+
# `Parameters` or an `Attributes` docstring section), don't also
76+
# synthesize a "Parameter Attributes" section — that would duplicate the
77+
# same fields. Which of the two sections to use is left to the docs writer.
78+
if (
79+
not self.is_dataclass
80+
or "Parameters" in titles
81+
or "Attributes" in titles
82+
or not len(self.parameters)
83+
):
7584
return items
7685

7786
# Create and insert Parameter Attributes

test-packages/synthetic/specs/gdtest_dataclasses.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22
gdtest_dataclasses — @dataclass objects.
33
44
Dimensions: A1, B1, C5, D1, E6, F6, G1, H7
5-
Focus: 3 dataclasses: one with default_factory, one frozen, one that also
6-
defines methods. Tests dataclass field documentation, __init__
7-
generation, and that a dataclass which defines methods still renders its
8-
constructor signature (regression: `Class.overloads` is a dict keyed by
9-
member name, so any class with methods was rendered through the overload
10-
path and lost its constructor parameters).
5+
Focus: 3 dataclasses: one with default_factory, one frozen (documented with an
6+
`Attributes` section), one that also defines methods. Tests dataclass
7+
field documentation, __init__ generation, that fields documented under
8+
either a `Parameters` or an `Attributes` section render once (no
9+
duplicated "Parameter Attributes"), and that a dataclass which defines
10+
methods still renders its constructor signature (regression:
11+
`Class.overloads` is a dict keyed by member name, so any class with
12+
methods was rendered through the overload path and lost its constructor
13+
parameters).
1114
"""
1215

1316
SPEC = {
@@ -62,7 +65,10 @@ class Record:
6265
"""
6366
An immutable data record.
6467
65-
Parameters
68+
Documented with an ``Attributes`` section (rather than
69+
``Parameters``) to exercise that rendering path.
70+
71+
Attributes
6672
----------
6773
id
6874
Unique record identifier.

tests/renderer/test_classes.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,3 +142,66 @@ class Widget:
142142
qmd = render_code_variable(code, "Widget")
143143

144144
assert "Widget(name, size=1)" in qmd
145+
146+
147+
def test_dataclass_attributes_section_not_duplicated():
148+
"""A dataclass documented with an `Attributes` section renders that section
149+
once, without a synthesized "Parameter Attributes" duplicate.
150+
151+
The author may document a dataclass's fields with either a `Parameters` or
152+
an `Attributes` section; great-docs must not also auto-generate a
153+
"Parameter Attributes" section listing the same fields.
154+
"""
155+
code = '''
156+
from dataclasses import dataclass
157+
158+
@dataclass
159+
class Widget:
160+
"""A widget.
161+
162+
Attributes
163+
----------
164+
name
165+
The widget name.
166+
size
167+
The widget size.
168+
"""
169+
170+
name: str
171+
size: int = 1
172+
'''
173+
qmd = render_code_variable(code, "Widget")
174+
175+
assert "## Attributes {.doc-attributes}" in qmd
176+
assert "## Parameter Attributes {.doc-parameter-attributes}" not in qmd
177+
# The author's descriptions must be preserved.
178+
assert "The widget name." in qmd
179+
180+
181+
def test_dataclass_parameters_section_not_duplicated():
182+
"""A dataclass documented with a `Parameters` section renders that section
183+
once, without a synthesized "Parameter Attributes" duplicate.
184+
"""
185+
code = '''
186+
from dataclasses import dataclass
187+
188+
@dataclass
189+
class Widget:
190+
"""A widget.
191+
192+
Parameters
193+
----------
194+
name
195+
The widget name.
196+
size
197+
The widget size.
198+
"""
199+
200+
name: str
201+
size: int = 1
202+
'''
203+
qmd = render_code_variable(code, "Widget")
204+
205+
assert "## Parameters {.doc-parameters}" in qmd
206+
assert "## Parameter Attributes {.doc-parameter-attributes}" not in qmd
207+
assert "The widget name." in qmd

tests/test_gdg_rendered.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -974,6 +974,37 @@ def test_dataclass_with_methods_signature_has_fields():
974974
)
975975

976976

977+
@pytest.mark.dedicated
978+
@requires_bs4
979+
def test_dataclass_attributes_section_not_duplicated():
980+
"""A dataclass documented with an `Attributes` section renders it once,
981+
without a synthesized "Parameter Attributes" duplicate.
982+
983+
Fields may be documented with either a `Parameters` or an `Attributes`
984+
section; great-docs must not also auto-generate a "Parameter Attributes"
985+
section listing the same fields. (`Record` in this package uses an
986+
`Attributes` section.)
987+
"""
988+
pkg = "gdtest_dataclasses"
989+
if not _has_rendered_site(pkg):
990+
pytest.skip("gdtest_dataclasses not rendered")
991+
992+
ref = _ref_dir(pkg)
993+
page = ref / "Record.html"
994+
if not page.exists():
995+
pytest.skip("Record.html not found")
996+
997+
soup = _load_html(page)
998+
999+
assert soup.select_one("section.doc-attributes") is not None, (
1000+
"Record.html: expected an Attributes section"
1001+
)
1002+
assert soup.select_one("section.doc-parameter-attributes") is None, (
1003+
"Record.html: 'Attributes'-documented dataclass should not also render a "
1004+
"synthesized 'Parameter Attributes' section"
1005+
)
1006+
1007+
9771008
@pytest.mark.dedicated
9781009
@requires_bs4
9791010
def test_async_functions_have_badge():

0 commit comments

Comments
 (0)