Path: blob/main/singlestoredb/docstring/util.py
469 views
"""Utility functions for working with docstrings."""1import typing as T2from collections import ChainMap3from inspect import Signature4from itertools import chain56from .common import DocstringMeta7from .common import DocstringParam8from .common import DocstringReturns # noqa: F4019from .common import DocstringStyle10from .common import RenderingStyle11from .parser import compose12from .parser import parse1314_Func = T.Callable[..., T.Any]151617def combine_docstrings(18*others: _Func,19exclude: T.Iterable[T.Type[DocstringMeta]] = (),20style: DocstringStyle = DocstringStyle.AUTO,21rendering_style: RenderingStyle = RenderingStyle.COMPACT,22) -> _Func:23"""A function decorator that parses the docstrings from `others`,24programmatically combines them with the parsed docstring of the decorated25function, and replaces the docstring of the decorated function with the26composed result. Only parameters that are part of the decorated functions27signature are included in the combined docstring. When multiple sources for28a parameter or docstring metadata exists then the decorator will first29default to the wrapped function's value (when available) and otherwise use30the rightmost definition from ``others``.3132The following example illustrates its usage:3334>>> def fun1(a, b, c, d):35... '''short_description: fun136...37... :param a: fun138... :param b: fun139... :return: fun140... '''41>>> def fun2(b, c, d, e):42... '''short_description: fun243...44... long_description: fun245...46... :param b: fun247... :param c: fun248... :param e: fun249... '''50>>> @combine_docstrings(fun1, fun2)51>>> def decorated(a, b, c, d, e, f):52... '''53... :param e: decorated54... :param f: decorated55... '''56>>> print(decorated.__doc__)57short_description: fun258<BLANKLINE>59long_description: fun260<BLANKLINE>61:param a: fun162:param b: fun163:param c: fun264:param e: fun265:param f: decorated66:returns: fun167>>> @combine_docstrings(fun1, fun2, exclude=[DocstringReturns])68>>> def decorated(a, b, c, d, e, f): pass69>>> print(decorated.__doc__)70short_description: fun271<BLANKLINE>72long_description: fun273<BLANKLINE>74:param a: fun175:param b: fun176:param c: fun277:param e: fun27879:param others: callables from which to parse docstrings.80:param exclude: an iterable of ``DocstringMeta`` subclasses to exclude when81combining docstrings.82:param style: style composed docstring. The default will infer the style83from the decorated function.84:param rendering_style: The rendering style used to compose a docstring.85:return: the decorated function with a modified docstring.86"""8788def wrapper(func: _Func) -> _Func:89sig = Signature.from_callable(func)9091comb_doc = parse(func.__doc__ or '')92docs = [parse(other.__doc__ or '') for other in others] + [comb_doc]93params = dict(94ChainMap(95*(96{param.arg_name: param for param in doc.params}97for doc in docs98),99),100)101102for doc in reversed(docs):103if not doc.short_description:104continue105comb_doc.short_description = doc.short_description106comb_doc.blank_after_short_description = (107doc.blank_after_short_description108)109break110111for doc in reversed(docs):112if not doc.long_description:113continue114comb_doc.long_description = doc.long_description115comb_doc.blank_after_long_description = (116doc.blank_after_long_description117)118break119120combined: T.Dict[T.Type[DocstringMeta], T.List[DocstringMeta]] = {}121for doc in docs:122metas: T.Dict[T.Type[DocstringMeta], T.List[DocstringMeta]] = {}123for meta in doc.meta:124meta_type = type(meta)125if meta_type in exclude:126continue127metas.setdefault(meta_type, []).append(meta)128for meta_type, meta_list in metas.items():129combined[meta_type] = meta_list130131combined[DocstringParam] = [132params[name] for name in sig.parameters if name in params133]134comb_doc.meta = list(chain(*combined.values()))135func.__doc__ = compose(136comb_doc, style=style, rendering_style=rendering_style,137)138return func139140return wrapper141142143