| """Utility functions for working with docstrings.""" |
|
|
| import typing as T |
| from collections import ChainMap |
| from inspect import Signature |
| from itertools import chain |
|
|
| from .common import ( |
| DocstringMeta, |
| DocstringParam, |
| DocstringReturns, |
| DocstringStyle, |
| RenderingStyle, |
| ) |
| from .parser import compose, parse |
|
|
| _Func = T.Callable[..., T.Any] |
|
|
| assert DocstringReturns |
|
|
|
|
| def combine_docstrings( |
| *others: _Func, |
| exclude: T.Iterable[T.Type[DocstringMeta]] = (), |
| style: DocstringStyle = DocstringStyle.AUTO, |
| rendering_style: RenderingStyle = RenderingStyle.COMPACT, |
| ) -> _Func: |
| """A function decorator that parses the docstrings from `others`, |
| programmatically combines them with the parsed docstring of the decorated |
| function, and replaces the docstring of the decorated function with the |
| composed result. Only parameters that are part of the decorated functions |
| signature are included in the combined docstring. When multiple sources for |
| a parameter or docstring metadata exists then the decorator will first |
| default to the wrapped function's value (when available) and otherwise use |
| the rightmost definition from ``others``. |
| |
| The following example illustrates its usage: |
| |
| >>> def fun1(a, b, c, d): |
| ... '''short_description: fun1 |
| ... |
| ... :param a: fun1 |
| ... :param b: fun1 |
| ... :return: fun1 |
| ... ''' |
| >>> def fun2(b, c, d, e): |
| ... '''short_description: fun2 |
| ... |
| ... long_description: fun2 |
| ... |
| ... :param b: fun2 |
| ... :param c: fun2 |
| ... :param e: fun2 |
| ... ''' |
| >>> @combine_docstrings(fun1, fun2) |
| >>> def decorated(a, b, c, d, e, f): |
| ... ''' |
| ... :param e: decorated |
| ... :param f: decorated |
| ... ''' |
| >>> print(decorated.__doc__) |
| short_description: fun2 |
| <BLANKLINE> |
| long_description: fun2 |
| <BLANKLINE> |
| :param a: fun1 |
| :param b: fun1 |
| :param c: fun2 |
| :param e: fun2 |
| :param f: decorated |
| :returns: fun1 |
| >>> @combine_docstrings(fun1, fun2, exclude=[DocstringReturns]) |
| >>> def decorated(a, b, c, d, e, f): pass |
| >>> print(decorated.__doc__) |
| short_description: fun2 |
| <BLANKLINE> |
| long_description: fun2 |
| <BLANKLINE> |
| :param a: fun1 |
| :param b: fun1 |
| :param c: fun2 |
| :param e: fun2 |
| |
| :param others: callables from which to parse docstrings. |
| :param exclude: an iterable of ``DocstringMeta`` subclasses to exclude when |
| combining docstrings. |
| :param style: style composed docstring. The default will infer the style |
| from the decorated function. |
| :param rendering_style: The rendering style used to compose a docstring. |
| :return: the decorated function with a modified docstring. |
| """ |
|
|
| def wrapper(func: _Func) -> _Func: |
| sig = Signature.from_callable(func) |
|
|
| comb_doc = parse(func.__doc__ or "") |
| docs = [parse(other.__doc__ or "") for other in others] + [comb_doc] |
| params = dict( |
| ChainMap( |
| *( |
| {param.arg_name: param for param in doc.params} |
| for doc in docs |
| ) |
| ) |
| ) |
|
|
| for doc in reversed(docs): |
| if not doc.short_description: |
| continue |
| comb_doc.short_description = doc.short_description |
| comb_doc.blank_after_short_description = ( |
| doc.blank_after_short_description |
| ) |
| break |
|
|
| for doc in reversed(docs): |
| if not doc.long_description: |
| continue |
| comb_doc.long_description = doc.long_description |
| comb_doc.blank_after_long_description = ( |
| doc.blank_after_long_description |
| ) |
| break |
|
|
| combined = {} |
| for doc in docs: |
| metas = {} |
| for meta in doc.meta: |
| meta_type = type(meta) |
| if meta_type in exclude: |
| continue |
| metas.setdefault(meta_type, []).append(meta) |
| for meta_type, meta in metas.items(): |
| combined[meta_type] = meta |
|
|
| combined[DocstringParam] = [ |
| params[name] for name in sig.parameters if name in params |
| ] |
| comb_doc.meta = list(chain(*combined.values())) |
| func.__doc__ = compose( |
| comb_doc, style=style, rendering_style=rendering_style |
| ) |
| return func |
|
|
| return wrapper |
|
|