Skip to content

Commit 86b54e6

Browse files
committed
Fix duplicated section items with complex class hierarchy
1 parent 70f01d6 commit 86b54e6

File tree

4 files changed

+136
-4
lines changed

4 files changed

+136
-4
lines changed

src/custom_inherit/_doc_parse_tools/napoleon_parse_tools.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
from collections import OrderedDict
44
from inspect import cleandoc
55

6+
from . import section_items
7+
68
__all__ = ["merge_google_napoleon_docs", "merge_numpy_napoleon_docs"]
79

810

@@ -53,6 +55,8 @@ def parse_napoleon_doc(doc, style):
5355

5456
doc_sections = OrderedDict([(key, None) for key in napoleon_sections])
5557

58+
section_items.set_defaults(doc_sections)
59+
5660
if not doc:
5761
return doc_sections
5862

@@ -84,6 +88,9 @@ def parse_napoleon_doc(doc, style):
8488
except StopIteration:
8589
doc_sections[aliases.get(key, key)] = "\n".join(body)
8690
break
91+
92+
section_items.parse(doc_sections)
93+
8794
return doc_sections
8895

8996

@@ -109,7 +116,7 @@ def merge_section(key, prnt_sec, child_sec, style, merge_within_sections=False):
109116
"Examples",
110117
]
111118

112-
if prnt_sec is None and child_sec is None:
119+
if not prnt_sec and not child_sec:
113120
return None
114121

115122
assert style in ("google", "numpy")
@@ -121,7 +128,11 @@ def merge_section(key, prnt_sec, child_sec, style, merge_within_sections=False):
121128
header = "\n".join((key, "".join("-" for i in range(len(key))), ""))
122129
else:
123130
header = "\n".join((key + ":", ""))
124-
if merge_within_sections and key not in napoleon_sections_that_cant_merge:
131+
132+
if key in section_items.SECTION_NAMES:
133+
body = section_items.merge(prnt_sec, child_sec, merge_within_sections, style)
134+
135+
elif merge_within_sections and key not in napoleon_sections_that_cant_merge:
125136
if child_sec is None:
126137
body = prnt_sec
127138
elif prnt_sec is None:

src/custom_inherit/_doc_parse_tools/numpy_parse_tools.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
from collections import OrderedDict
44
from inspect import cleandoc
55

6+
from . import section_items
7+
68
__all__ = ["merge_numpy_docs"]
79

810

@@ -37,6 +39,8 @@ def parse_numpy_doc(doc):
3739
]
3840
)
3941

42+
section_items.set_defaults(doc_sections)
43+
4044
if not doc:
4145
return doc_sections
4246

@@ -59,6 +63,8 @@ def parse_numpy_doc(doc):
5963
doc_sections[key] = "\n".join(body)
6064
break
6165

66+
section_items.parse(doc_sections)
67+
6268
return doc_sections
6369

6470

@@ -85,15 +91,18 @@ def merge_section(key, prnt_sec, child_sec, merge_within_sections=False):
8591
"Examples"
8692
]
8793

88-
if prnt_sec is None and child_sec is None:
94+
if not prnt_sec and not child_sec:
8995
return None
9096

9197
if key == "Short Summary":
9298
header = ""
9399
else:
94100
header = "\n".join((key, "".join("-" for i in range(len(key))), ""))
95101

96-
if merge_within_sections and key not in doc_sections_that_cant_merge:
102+
if key in section_items.SECTION_NAMES:
103+
body = section_items.merge(prnt_sec, child_sec, merge_within_sections, "numpy")
104+
105+
elif merge_within_sections and key not in doc_sections_that_cant_merge:
97106
if child_sec is None:
98107
body = prnt_sec
99108
elif prnt_sec is None:
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
"""This module handles sections with items."""
2+
3+
from collections import OrderedDict
4+
import inspect
5+
import re
6+
7+
try:
8+
from textwrap import indent
9+
except ImportError:
10+
# for Python < 3.3
11+
def indent(text, padding):
12+
return ''.join(padding+line for line in text.splitlines(True))
13+
14+
15+
_RE_PATTERN_ITEMS = re.compile(
16+
r"(\**\w+)(.*?)(?:$|(?=\n\**\w+))", flags=re.DOTALL
17+
)
18+
19+
_STYLE_TO_PADDING = {
20+
"numpy": "",
21+
"google": " " * 4,
22+
}
23+
24+
SECTION_NAMES = ("Attributes", "Parameters")
25+
26+
27+
def _render(body, style):
28+
"""Render the items of a section.
29+
30+
Parameters
31+
----------
32+
body: OrderedDict[str, Optional[str]]
33+
The items of a section.
34+
style: str
35+
The doc style.
36+
37+
Returns
38+
-------
39+
str
40+
"""
41+
padding = _STYLE_TO_PADDING[style]
42+
section = []
43+
for key, value in body.items():
44+
section += [indent("{}{}".format(key, value), padding)]
45+
return "\n".join(section)
46+
47+
48+
def set_defaults(doc_sections):
49+
"""Set the defaults for the sections with items in place.
50+
51+
Parameters
52+
----------
53+
doc_sections: OrderedDict[str, Optional[str]]
54+
"""
55+
for section_name in SECTION_NAMES:
56+
doc_sections[section_name] = OrderedDict()
57+
58+
59+
def parse(doc_sections):
60+
"""Parse the sections with items in place.
61+
62+
Parameters
63+
----------
64+
doc_sections: OrderedDict[str, Optional[str]]
65+
"""
66+
for section_name in SECTION_NAMES:
67+
section_content = doc_sections[section_name]
68+
if section_content:
69+
doc_sections[section_name] = OrderedDict(_RE_PATTERN_ITEMS.findall(inspect.cleandoc(section_content)))
70+
71+
72+
def merge(prnt_sec, child_sec, merge_within_sections, style):
73+
"""Merge the doc-sections of the parent's and child's attribute with items.
74+
75+
Parameters
76+
----------
77+
prnt_sec: OrderedDict[str, str]
78+
child_sec: OrderedDict[str, str]
79+
merge_within_sections: bool
80+
Wheter to merge the items.
81+
style: str
82+
The doc style.
83+
84+
Returns
85+
-------
86+
OrderedDict[str, str]
87+
The merged items.
88+
"""
89+
if merge_within_sections:
90+
body = prnt_sec.copy()
91+
body.update(child_sec)
92+
body = _render(body, style)
93+
else:
94+
body = prnt_sec if not child_sec else child_sec
95+
body = _render(body, style)
96+
return body

tests/metaclass_test.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
from types import FunctionType, MethodType
44

55
from six import add_metaclass
6+
import pytest
67

78
from custom_inherit import DocInheritMeta
9+
from custom_inherit._doc_parse_tools.section_items import _RE_PATTERN_ITEMS
810

911
try:
1012
from inspect import signature
@@ -307,3 +309,17 @@ def test_special_method3():
307309
# __init__ docstring should inherit from Parent3
308310
assert isinstance(Kid3().__init__, MethodType)
309311
assert getdoc(Kid3.__init__) == "valid"
312+
313+
314+
@pytest.mark.parametrize(
315+
"section_content,expected",
316+
(
317+
("foo", [("foo", "")]),
318+
("foo : str\n Foo.", [("foo", " : str\n Foo.")]),
319+
("foo\nbar", [("foo", ""), ("bar", "")]),
320+
("foo : str\n Foo.\nbar", [("foo", " : str\n Foo."), ("bar", "")]),
321+
("foo : str\n Foo.\nbar : int\n Bar.", [("foo", " : str\n Foo."), ("bar", " : int\n Bar.")]),
322+
)
323+
)
324+
def test_regex(section_content, expected):
325+
assert _RE_PATTERN_ITEMS.findall(section_content) == expected

0 commit comments

Comments
 (0)