Skip to content

Commit 27c7f7a

Browse files
authored
Merge pull request #31 from Boubsi/inner_section_merging
Inner section merging and special method option
2 parents 56cfc9e + e56d417 commit 27c7f7a

File tree

8 files changed

+698
-54
lines changed

8 files changed

+698
-54
lines changed

README.md

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,10 @@ class Parent(metaclass=DocInheritMeta(style="numpy")):
5353
----------
5454
x: int
5555
blah-x
56-
56+
5757
y: Optional[int]
5858
blah-y
59-
59+
6060
Raises
6161
------
6262
NotImplementedError"""
@@ -85,7 +85,7 @@ Because we specified `style="numpy"` in `DocInheritMeta`, the inherited docstrin
8585
----------
8686
x: int
8787
blah-x
88-
88+
8989
y: Optional[int]
9090
blah-y
9191
@@ -146,44 +146,69 @@ class Parent(metaclass=DocInheritMeta(style="numpy", abstract_base_class=True)):
146146

147147
For the "numpy", "google", and "napoleon_numpy" inheritance styles, one then only needs to specify the "Returns" or "Yields" section in the derived class' attribute docstring for it to have a fully-detailed docstring.
148148

149+
Another option is to be able to decide whether to include all special methods, meaning methods that start and
150+
end by "__" such as "__init__" method, or not in the doctstring inheritance process. Such an option can be pass
151+
to the `DocInheritMeta` metaclass constructor:
152+
153+
```python
154+
# Each child class will also merge the special methods' docstring of its parent class
155+
class Parent(metaclass=DocInheritMeta(style="numpy", include_special_methods=True)):
156+
...
157+
```
158+
159+
Special methods are not included by default.
160+
149161
## Built-in Styles
150162

151163
Utilize a built-in style by specifying any of the following names (as a string), wherever the `style` parameter is to be specified. The built-in styles are:
152164

153165
- `"parent"`: Wherever the docstring for a child-class' attribute (or for the class itself) is
154166
`None`, inherit the corresponding docstring from the parent. (Deprecated in Python 3.5)
155167

156-
- `"numpy"`: [NumPy-styled docstrings](https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt#docstring-standard)
157-
from the parent and child are merged gracefully with nice formatting. The child's docstring sections take precedence
158-
in the case of overlap.
168+
- `"numpy"`: [NumPy-styled docstrings](https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt#docstring-standard)
169+
from the parent and child are merged gracefully with nice formatting. The child's docstring sections take precedence
170+
in the case of overlap.
171+
172+
- `"numpy_with_merge"`: Behaves identically to the "numpy" style, but also merges sections that overlap,
173+
instead of only keeping the child's section. All sections are concerned except sections "Short Summary",
174+
"Extended Summary", "Deprecation Warning" and "Examples" for which the "numpy" style behaviour applies.
159175

160176
- `"google"`: Google-styled docstrings from the parent and child are merged gracefully
161177
with nice formatting. The child's docstring sections take precedence in the case of overlap.
162178
This adheres to the [napoleon specification for the Google style](http://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html#example-google-style-python-docstrings).
163179

180+
- `"google_with_merge"`: Behaves identically to the "google" style, but also merges sections that overlap,
181+
instead of only keeping the child's section. All sections are concerned except sections "Short Summary",
182+
"Example" and "Examples" (or coresponding aliases) for which the 'google' style applies.
183+
164184
- `"numpy_napoleon"`: NumPy-styled docstrings from the parent and child are merged gracefully
165185
with nice formatting. The child's docstring sections take precedence in the case of overlap.
166186
This adheres to the [napoleon specification for the NumPy style](http://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_numpy.html#example-numpy).
167187

188+
- `"numpy_napoleon_with_merge"`: Behaves identically to the 'numpy_napoleon' style, but also merges sections
189+
that overlap, instead of only keeping the child's section. All sections are concerned except sections
190+
"Short Summary", "Example" and "Examples" (or coresponding aliases) for which the 'numpy_napoleon' style
191+
behaviour applies.
192+
168193
- `"reST"`: reST-styled docstrings from the parent and child are merged gracefully
169-
with nice formatting. Docstring sections are specified by
194+
with nice formatting. Docstring sections are specified by
170195
[reST section titles](http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html#sections).
171196
The child's docstring sections take precedence in the case of overlap.
172197

173-
For the `numpy`, `numpy_napoleon`, and `google` styles, if the parent's docstring contains a "Raises" section and the child's docstring implements a "Returns" or a "Yields" section instead, then the "Raises" section is not included in the resulting docstring. This is to accomodate for the relatively common use case in which an abstract method/property raises `NotImplementedError`. Child classes that implement this method/property clearly will not raise this. Of course, any "Raises" section that is explicitly included in the child's docstring will appear in the resulting docstring.
198+
For the `numpy`, `numpy_with_merge`, `numpy_napoleon`, `numpy_napoleon_with_merge`, `google` and `google_with_merge` styles, if the parent's docstring contains a "Raises" section and the child's docstring implements a "Returns" or a "Yields" section instead, then the "Raises" section is not included in the resulting docstring. This is to accomodate for the relatively common use case in which an abstract method/property raises `NotImplementedError`. Child classes that implement this method/property clearly will not raise this. Of course, any "Raises" section that is explicitly included in the child's docstring will appear in the resulting docstring.
174199

175200
Detailed documentation and example cases for the default styles can be found [here](https://github.com/meowklaski/custom_inherit/blob/master/custom_inherit/_style_store.py)
176201

177202
## Making New Inheritance Styles
178-
Implementing your inheritance style is simple.
203+
Implementing your inheritance style is simple.
179204

180205
- Provide an inheritance style on the fly wherever a style parameter is specified:
181206
- Supply any function of the form: `func(prnt_doc: str, child_doc: str) -> str`
182207

183208
- Log an inheritance style, and refer to it by name wherever a style parameter is specified, using either:
184-
- `custom_inherit.store["my_style"] = func`
185-
- `custom_inherit.add_style("my_style", func)`.
186-
209+
- `custom_inherit.store["my_style"] = func`
210+
- `custom_inherit.add_style("my_style", func)`.
211+
187212
## Installation and Getting Started
188213
Install via pip:
189214

@@ -193,7 +218,7 @@ Install via pip:
193218
Install via conda:
194219

195220
```
196-
conda install -c conda-forge custom-inherit
221+
conda install -c conda-forge custom-inherit
197222
```
198223

199224
or
@@ -251,7 +276,7 @@ custom_inherit.doc_inherit(parent, style="parent"):
251276
Parameters
252277
----------
253278
parent : Union[str, Any]
254-
The object whose docstring, is utilized as the parent docstring
279+
The object whose docstring, is utilized as the parent docstring
255280
during the docstring merge. Or, a string can be provided directly.
256281
257282
style : Union[Hashable, Callable[[str, str], str]], optional (default: "parent")

src/custom_inherit/__init__.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@
44

55
from ._decorator_base import DocInheritDecorator as _DocInheritDecorator
66
from ._metaclass_base import DocInheritorBase as _DocInheritorBase
7-
from ._style_store import google, numpy, numpy_napoleon, parent, reST
7+
from . import _style_store
8+
from ._style_store import (
9+
google, numpy, numpy_napoleon, parent, reST,
10+
google_with_merge, numpy_napoleon_with_merge, numpy_with_merge
11+
)
812
from ._version import get_versions
913

1014
__version__ = get_versions()["version"]
@@ -113,7 +117,6 @@ def items(self):
113117
""" D.items() -> a set-like object providing a view on D's items"""
114118
return self._store.items()
115119

116-
117120
store = _Store([(key, getattr(_style_store, key)) for key in _style_store.__all__])
118121

119122

@@ -140,7 +143,7 @@ def remove_style(style):
140143
store.pop(style)
141144

142145

143-
def DocInheritMeta(style="parent", abstract_base_class=False):
146+
def DocInheritMeta(style="parent", abstract_base_class=False, include_special_methods=False):
144147
""" A metaclass that merges the respective docstrings of a parent class and of its child, along with their
145148
properties, methods (including classmethod, staticmethod, decorated methods).
146149
@@ -156,13 +159,18 @@ def DocInheritMeta(style="parent", abstract_base_class=False):
156159
will be an abstract base class, whose derived classes will inherit docstrings
157160
using the numpy-style inheritance scheme.
158161
162+
include_special_methods: bool, optional (defaul: False)
163+
Wether special methods of class (i.e. starting en ending with "__") are included in the docstring
164+
inheritance process.
165+
159166
160167
Returns
161168
-------
162169
custom_inherit.DocInheritorBase"""
163170

164171
merge_func = store[style]
165172
metaclass = _DocInheritorBase
173+
metaclass.include_special_methods = include_special_methods
166174
metaclass.class_doc_inherit = staticmethod(merge_func)
167175
metaclass.attr_doc_inherit = staticmethod(merge_func)
168176

src/custom_inherit/_doc_parse_tools/napoleon_parse_tools.py

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def parse_napoleon_doc(doc, style):
8787
return doc_sections
8888

8989

90-
def merge_section(key, prnt_sec, child_sec, style):
90+
def merge_section(key, prnt_sec, child_sec, style, merge_within_sections=False):
9191
""" Synthesize a output napoleon docstring section.
9292
9393
Parameters
@@ -102,6 +102,13 @@ def merge_section(key, prnt_sec, child_sec, style):
102102
-------
103103
Optional[str]
104104
The output docstring section."""
105+
106+
napoleon_sections_that_cant_merge = [
107+
"Short Summary",
108+
"Example",
109+
"Examples",
110+
]
111+
105112
if prnt_sec is None and child_sec is None:
106113
return None
107114

@@ -114,13 +121,20 @@ def merge_section(key, prnt_sec, child_sec, style):
114121
header = "\n".join((key, "".join("-" for i in range(len(key))), ""))
115122
else:
116123
header = "\n".join((key + ":", ""))
117-
118-
body = prnt_sec if child_sec is None else child_sec
124+
if merge_within_sections and key not in napoleon_sections_that_cant_merge:
125+
if child_sec is None:
126+
body = prnt_sec
127+
elif prnt_sec is None:
128+
body = child_sec
129+
else:
130+
body = '\n'.join((prnt_sec, child_sec))
131+
else:
132+
body = prnt_sec if child_sec is None else child_sec
119133

120134
return header + body
121135

122136

123-
def merge_all_sections(prnt_sctns, child_sctns, style):
137+
def merge_all_sections(prnt_sctns, child_sctns, style, merge_within_sections=False):
124138
""" Merge the doc-sections of the parent's and child's attribute into a single docstring.
125139
126140
Parameters
@@ -141,13 +155,19 @@ def merge_all_sections(prnt_sctns, child_sctns, style):
141155
prnt_sctns["Raises"] = None
142156

143157
for key in prnt_sctns:
144-
sect = merge_section(key, prnt_sctns[key], child_sctns[key], style)
158+
sect = merge_section(
159+
key,
160+
prnt_sctns[key],
161+
child_sctns[key],
162+
style,
163+
merge_within_sections=merge_within_sections
164+
)
145165
if sect is not None:
146166
doc.append(sect)
147167
return "\n\n".join(doc) if doc else None
148168

149169

150-
def merge_numpy_napoleon_docs(prnt_doc=None, child_doc=None):
170+
def merge_numpy_napoleon_docs(prnt_doc=None, child_doc=None, merge_within_sections=False):
151171
""" Merge two numpy-style docstrings into a single docstring, according to napoleon docstring sections.
152172
153173
Given the numpy-style docstrings from a parent and child's attributes, merge the docstring
@@ -172,11 +192,14 @@ def merge_numpy_napoleon_docs(prnt_doc=None, child_doc=None):
172192
The merged docstring. """
173193
style = "numpy"
174194
return merge_all_sections(
175-
parse_napoleon_doc(prnt_doc, style), parse_napoleon_doc(child_doc, style), style
195+
parse_napoleon_doc(prnt_doc, style),
196+
parse_napoleon_doc(child_doc, style),
197+
style,
198+
merge_within_sections=merge_within_sections
176199
)
177200

178201

179-
def merge_google_napoleon_docs(prnt_doc=None, child_doc=None):
202+
def merge_google_napoleon_docs(prnt_doc=None, child_doc=None, merge_within_sections=False):
180203
""" Merge two google-style docstrings into a single docstring, according to napoleon docstring sections.
181204
182205
Given the google-style docstrings from a parent and child's attributes, merge the docstring
@@ -201,5 +224,8 @@ def merge_google_napoleon_docs(prnt_doc=None, child_doc=None):
201224
The merged docstring. """
202225
style = "google"
203226
return merge_all_sections(
204-
parse_napoleon_doc(prnt_doc, style), parse_napoleon_doc(child_doc, style), style
227+
parse_napoleon_doc(prnt_doc, style),
228+
parse_napoleon_doc(child_doc, style),
229+
style,
230+
merge_within_sections=merge_within_sections
205231
)

src/custom_inherit/_doc_parse_tools/numpy_parse_tools.py

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def parse_numpy_doc(doc):
6161
return doc_sections
6262

6363

64-
def merge_section(key, prnt_sec, child_sec):
64+
def merge_section(key, prnt_sec, child_sec, merge_within_sections=False):
6565
""" Synthesize a output numpy docstring section.
6666
6767
Parameters
@@ -76,6 +76,14 @@ def merge_section(key, prnt_sec, child_sec):
7676
-------
7777
Optional[str]
7878
The output docstring section."""
79+
80+
doc_sections_that_cant_merge = [
81+
"Short Summary",
82+
"Deprecation Warning",
83+
"Extended Summary",
84+
"Examples"
85+
]
86+
7987
if prnt_sec is None and child_sec is None:
8088
return None
8189

@@ -84,15 +92,20 @@ def merge_section(key, prnt_sec, child_sec):
8492
else:
8593
header = "\n".join((key, "".join("-" for i in range(len(key))), ""))
8694

87-
if child_sec is None:
88-
body = prnt_sec
95+
if merge_within_sections and key not in doc_sections_that_cant_merge:
96+
if child_sec is None:
97+
body = prnt_sec
98+
elif prnt_sec is None:
99+
body = child_sec
100+
else:
101+
body = '\n'.join((prnt_sec, child_sec))
89102
else:
90-
body = child_sec
103+
body = prnt_sec if child_sec is None else child_sec
91104

92105
return header + body
93106

94107

95-
def merge_all_sections(prnt_sctns, child_sctns):
108+
def merge_all_sections(prnt_sctns, child_sctns, merge_within_sections=False):
96109
""" Merge the doc-sections of the parent's and child's attribute into a single docstring.
97110
98111
Parameters
@@ -113,13 +126,18 @@ def merge_all_sections(prnt_sctns, child_sctns):
113126
prnt_sctns["Raises"] = None
114127

115128
for key in prnt_sctns:
116-
sect = merge_section(key, prnt_sctns[key], child_sctns[key])
129+
sect = merge_section(
130+
key,
131+
prnt_sctns[key],
132+
child_sctns[key],
133+
merge_within_sections=merge_within_sections
134+
)
117135
if sect is not None:
118136
doc.append(sect)
119137
return "\n\n".join(doc) if doc else None
120138

121139

122-
def merge_numpy_docs(prnt_doc=None, child_doc=None):
140+
def merge_numpy_docs(prnt_doc=None, child_doc=None, merge_within_sections=False):
123141
""" Merge two numpy-style docstrings into a single docstring.
124142
125143
Given the numpy-style docstrings from a parent and child's attributes, merge the docstring
@@ -141,4 +159,8 @@ def merge_numpy_docs(prnt_doc=None, child_doc=None):
141159
Union[str, None]
142160
The merged docstring.
143161
"""
144-
return merge_all_sections(parse_numpy_doc(prnt_doc), parse_numpy_doc(child_doc))
162+
return merge_all_sections(
163+
parse_numpy_doc(prnt_doc),
164+
parse_numpy_doc(child_doc),
165+
merge_within_sections=merge_within_sections
166+
)

src/custom_inherit/_metaclass_base.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ class DocInheritorBase(type):
2121
This merge-style must be implemented via the static methods `class_doc_inherit`
2222
and `attr_doc_inherit`, which are set within `custom_inherit.DocInheritMeta`."""
2323

24+
include_special_methods = False
25+
2426
def __new__(mcs, class_name, class_bases, class_dict):
2527
# inherit class docstring: the docstring is constructed by traversing
2628
# the mro for the class and merging their docstrings, with each next
@@ -42,8 +44,10 @@ def __new__(mcs, class_name, class_bases, class_dict):
4244
attribute,
4345
(FunctionType, MethodType, classmethod, staticmethod, property),
4446
)
45-
46-
if (attr.startswith("__") and attr.endswith("__")) or not is_doc_type:
47+
if (
48+
(attr.startswith("__") and attr.endswith("__") and not mcs.include_special_methods) or
49+
not is_doc_type
50+
):
4751
continue
4852

4953
is_static_or_class = isinstance(attribute, (staticmethod, classmethod))

0 commit comments

Comments
 (0)