"""
Package for useful functionalities not belonging to any other cathegory.
"""
__all__ = ["inherit_docstrings_from_superclass"]
import types
import re
[docs]def inherit_docstrings_from_superclass(cls, doc_inheritance_specifier="inherit_doc::"):
"""
Add docstrings for methods of the class from its superclasses and return it.
Every function from the given class without a __doc__-attribute or with
a __doc__-attribute containing the given specifier will get a copy of
a __doc__-attribute from a function with the same name from one of its superclasses.
This can be used as a decorator.
This function was taken and modified from the 'Stack Overflow' network.
The original author is Raymond Hettinger.
- original source: https://stackoverflow.com/a/8101598
answered by: Raymond Hettinger (https://stackoverflow.com/users/1001643/raymond-hettinger)
- original question: https://stackoverflow.com/q/8100166
asked by: Fred Foo (https://stackoverflow.com/users/166749/fred-foo)
The original is licensed under Creative Commons Attribution-Share Alike.
This function, the functions it relies on included this file and
its documentation are therefore licensed under the same license (Adapter's License).
You can find a copy of this license here: https://creativecommons.org/licenses/by-sa/4.0/legalcode
And a more readable summary here: https://creativecommons.org/licenses/by-sa/4.0/
The doc_inheritance_specifier will be interpreted similar to a reStructuredText-directive.
It will accept *one* the following parameters:
description: Instead of replacing the specifier and this parameter with all of the superclass'
__doc__-attribute, only the description above the sections ``Args`` and ``Returns`` will be copied.
arguments: Instead of replacing the specifier and this parameter with all of the superclass'
__doc__-attribute, only the section ``Args`` will be copied.
return_values: Instead of replacing the specifier and this parameter with all of the superclass'
__doc__-attribute, only the section ``Returns`` will be copied.
all: Replaces the specifier and this parameter with all of the superclass' __doc__-attribute,
same as if no parameter was given.
Any replacement string from a superclass will have leading and trailing whitespace stripped off
so it integrates seamlessly into existing docstrings.
Args:
cls: A class to modify.
doc_inheritance_specifier: A string to search for in the __doc__-attributes of functions.
If this string is found, it will be replaced with the found __doc__ from a superclass;
the rest of the __doc__-attribute remains as before.
If this is a falsy value (e.g. None), no such specifier will be searched for;
instead only functions without a __doc__ attribute will inherit docstrings from superclasses.
Returns:
The class supplied (with the modifications made).
"""
for name, func in vars(cls).items():
if isinstance(func, types.FunctionType):
if func.__doc__ and (not doc_inheritance_specifier or doc_inheritance_specifier not in func.__doc__):
continue
try:
superclasses = cls.__mro__[1:]
except:
superclasses = cls.__bases__
for supercls in superclasses:
superfunc = getattr(supercls, name, None)
if superfunc and getattr(superfunc, "__doc__", None):
if func.__doc__:
matches = _extract_inheritance_parameters_from_doc(func.__doc__, doc_inheritance_specifier)
sections = extract_sections_from_doc(superfunc.__doc__)
replaced_all = True
for match in matches:
parameter = match.group(1)
if not parameter:
parameter = "all"
if parameter in sections:
func.__doc__ = func.__doc__[:match.start()] + sections[parameter].strip() + func.__doc__[match.end():]
else:
replaced_all = False
if not replaced_all:
continue
else:
func.__doc__ = superfunc.__doc__
break
else:
if func.__doc__:
func.__doc__ = func.__doc__.replace(doc_inheritance_specifier, "")
return cls
def extract_sections_from_doc(doc):
"""
Extract sections from a given docstring.
It will search for the following sections:
description: the description above the sections ``Args`` and ``Returns``
arguments: the section ``Args``
return_values: the section ``Returns``
all: all of the given docstring
Args:
doc: A (doc-)string to extract sections from.
Returns:
A dict with section-names as keys and their content as values.
"""
sections = {"all": doc}
index = 0
def section_generator():
last_index = -1
possible = ("description", "arguments", "return_values")
while index < len(possible):
if last_index != index:
sections[possible[index]] = []
last_index = index
yield possible[index]
lines = doc.split("\n")
for line, section in zip(lines, section_generator()):
if line.strip() in ("Args:", "Returns:"):
index += 1
else:
sections[section].append(line)
return {key: "\n".join(value) if isinstance(value, list) else value for key, value in sections.items()}
def _extract_inheritance_parameters_from_doc(doc, doc_inheritance_specifier=None):
"""
Return a iterator of Match-objects (from module re) with the span equal to the span of the "doc_inheritance-directive"
and the group equal to the parameter. (If there was no parameter, the group is None.)
This is an internal function.
Args:
doc: A (doc-)string to extract matches from.
doc_inheritance_specifier: A string to search for in the doc-attribute.
If this is a falsy value (e.g. None), the default value
'inherit_doc::' will be used.
Returns:
The iterator with the Match-objects.
"""
if not doc_inheritance_specifier:
doc_inheritance_specifier = "inherit_doc::"
pattern = re.compile("".join(map(lambda s: "[" + s + "]", doc_inheritance_specifier)) + "(?:[ ](\\S*))?", re.MULTILINE)
return re.finditer(pattern, doc)