Skip to content

Commit 8894116

Browse files
authored
Merge pull request #40 from AntoineD/fix-duplicated-attr
Fix duplicated section items with complex class hierarchy
2 parents 8bdab4c + 86b54e6 commit 8894116

File tree

4 files changed

+186
-4
lines changed

4 files changed

+186
-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: 66 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
@@ -240,6 +242,56 @@ class Child(Mixin, Parent):
240242
)
241243

242244

245+
def test_class_docstring_merge_hierarchy_numpy():
246+
@add_metaclass(DocInheritMeta(style="numpy_with_merge"))
247+
class GrandParent(object):
248+
"""GrandParent.
249+
250+
Attributes
251+
----------
252+
foo
253+
"""
254+
255+
class Parent(GrandParent):
256+
"""
257+
Attributes
258+
----------
259+
bar
260+
"""
261+
262+
class Child(Parent):
263+
pass
264+
265+
assert (
266+
getdoc(Child)
267+
== "GrandParent.\n\nAttributes\n----------\nfoo\nbar"
268+
)
269+
270+
271+
def test_class_docstring_merge_hierarchy_google():
272+
@add_metaclass(DocInheritMeta(style="google_with_merge"))
273+
class GrandParent(object):
274+
"""GrandParent.
275+
276+
Args:
277+
foo
278+
"""
279+
280+
class Parent(GrandParent):
281+
"""
282+
Args:
283+
bar
284+
"""
285+
286+
class Child(Parent):
287+
pass
288+
289+
assert (
290+
getdoc(Child)
291+
== "GrandParent.\n\nParameters:\n foo\n bar"
292+
)
293+
294+
243295
""" Include special method option"""
244296

245297
@add_metaclass(DocInheritMeta(style=style, include_special_methods=True))
@@ -257,3 +309,17 @@ def test_special_method3():
257309
# __init__ docstring should inherit from Parent3
258310
assert isinstance(Kid3().__init__, MethodType)
259311
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)