clazz.py 2.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
import types
from functools import reduce
from operator import __or__
from typing import Iterable, TypeVar

__all__ = [
    'tag_names', 'inherit_names', 'current_names',
]

_DOC_TAG = '__doc_names__'
_CLS_TYPE = TypeVar('_CLS_TYPE', bound=type)


def _get_names(clazz: type):
    return set(getattr(clazz, _DOC_TAG, set()))


def _set_names(clazz: type, names: Iterable[str]):
    setattr(clazz, _DOC_TAG, set(names))


def tag_names(names: Iterable[str], keep: bool = True):
    def _decorator(cls: _CLS_TYPE) -> _CLS_TYPE:
        _old_names = _get_names(cls) if keep else set()
        _set_names(cls, set(names) | _old_names)

        return cls

    return _decorator


def inherit_names(*clazzes: type, keep: bool = True):
    def _decorator(cls: _CLS_TYPE) -> _CLS_TYPE:
        _old_names = _get_names(cls) if keep else set()
        _set_names(cls, reduce(__or__, [_old_names, *map(_get_names, clazzes)]))
        return cls

    return _decorator


class _TempClazz:
    @property
    def prop(self):
        return None


PropertyType = type(_TempClazz.prop)


# noinspection PyTypeChecker
def _is_func_property(clazz, name):
    func = getattr(clazz, name)
    return isinstance(func, (types.FunctionType, PropertyType)) and (
            not hasattr(clazz.__base__, name) or getattr(clazz.__base__, name) is not func
    )


def _is_classmethod(clazz, name):
    method = getattr(clazz, name)
    return isinstance(method, types.MethodType) and (
            not hasattr(clazz.__base__, name) or getattr(clazz.__base__, name).__func__ is not method.__func__
    )


def current_names(keep: bool = True):
    def _decorator(cls: _CLS_TYPE) -> _CLS_TYPE:
        members = set()
        for name in dir(cls):
            item = getattr(cls, name)
            if (_is_func_property(cls, name) or _is_classmethod(cls, name)) and \
                    getattr(item, '__name__', None) == name:  # should be public or protected
                members.add(name)

        _old_names = _get_names(cls) if keep else set()
        _set_names(cls, _old_names | set(members))
        return cls

    return _decorator