nstarman/utilipy

View on GitHub
utilipy/utils/metaclasses.py

Summary

Maintainability
A
1 hr
Test Coverage
# -*- coding: utf-8 -*-

"""Metaclasses."""

__author__ = "Nathaniel Starkman"
__credits__ = ["astropy"]


##############################################################################
# IMPORTS

# BUILT-IN
import inspect
import typing as T

##############################################################################
# CODE


class InheritDocstrings(type):
    """Docstring inheritance metaclass.

    This metaclass makes methods of a class automatically have their
    docstrings filled in from the methods they override in the base
    class.
    If the class uses multiple inheritance, the docstring will be
    chosen from the first class in the bases list, in the same way as
    methods are normally resolved in Python.  If this results in
    selecting the wrong docstring, the docstring will need to be
    explicitly included on the method.

    For example::
        >>> class A(metaclass=InheritDocstrings):
        ...     def wiggle(self):
        ...         "Wiggle the thingamajig"
        ...         pass
        >>> class B(A):
        ...     def wiggle(self):
        ...         pass
        >>> B.wiggle.__doc__
        'Wiggle the thingamajig'

    taken from astropy

    .. todo::

        - add docparse tools so can merge docstrings

    """

    def __init__(cls, name: str, bases: T.Any, dct: T.Dict):
        """Set up docstring inheritance."""

        def is_public_member(key):
            return (
                key.startswith("__") and key.endswith("__") and len(key) > 4
            ) or not key.startswith("_")

        # /def

        for key, val in dct.items():
            if (
                (inspect.isfunction(val) or inspect.isdatadescriptor(val))
                and is_public_member(key)
                and val.__doc__ is None
            ):
                for base in cls.__mro__[1:]:
                    super_method = getattr(base, key, None)
                    if super_method is not None:
                        val.__doc__ = super_method.__doc__
                        break
                # /for
        # /for

        super().__init__(name, bases, dct)
        return

    # /def


# /class

##############################################################################
# END