Source code for isimple.util.meta

import inspect
from typing import List, Union, Dict, Any, Collection, _GenericAlias  # type: ignore


[docs]def describe_function(f): name = f.__name__ if inspect.ismethod(f): if hasattr(f, '__self__'): classes = [f.__self__.__class__] else: #unbound method or regular function classes = [f.im_class] while classes: c = classes.pop() if name in c.__dict__: return f'{f.__module__}.{c.__name__}.{name}' else: classes = list(c.__bases__) + classes return f'{f.__module__}{name}'
[docs]def bases(c: type) -> List[type]: b = [base for base in c.__bases__] for base in b: b += bases(base) return list(set(b))
[docs]def nbases(c: type) -> int: if c is None or c is type(None): return 0 else: return len(bases(c))
[docs]def all_attributes( t: Union[object, type], include_under: bool = True, include_methods: bool = True, include_mro: bool = False, ) -> List[str]: if not isinstance(t, type): t = t.__class__ b = [t] + bases(t) attributes: list = [] for base in b: attributes += base.__dict__ attributes = list(set(attributes)) if not include_under: attributes = [a for a in attributes if a[0] != '_'] if not include_methods: attributes = [a for a in attributes if not hasattr(getattr(t,a),'__call__')] # todo: this is hacky if not include_mro: attributes = [a for a in attributes if a[0:3] != 'mro'] return attributes
[docs]def all_annotations(t: Union[object, type]) -> Dict[str, type]: if not isinstance(t, type): t = object.__class__ b = [t] + bases(t) annotations: Dict[str, Any] = {} # todo: in order to get the correct annotation, bases must be ordered from most generic to most specific for base in sorted(b, key=nbases): try: annotations.update(base.__annotations__) except AttributeError: pass return annotations
[docs]def get_overridden_methods(c, m) -> list: b = [c] + bases(c) implementations = [] for base in b: if m.__name__ in base.__dict__: implementations.append(getattr(base, m.__name__)) return implementations
[docs]def resolve_type_to_most_specific(t: _GenericAlias) -> _GenericAlias: """Resolve Union in a type annotation to its most specific element * Use case: todo: extend to Optional :param t: :return: """ if hasattr(t, '__origin__'): if t.__origin__ == Union: # Return the argument with the highest number of bases # * If there are multiple 'specific options', return the first one (!) # * Doesn't cover nested Union which seems to be resolved to # a flat Union at runtime anyway. candidates = tuple( [a for a in t.__args__ if nbases(a) == nbases(max(t.__args__, key=nbases))] ) if len(candidates) == 1: return candidates[0] else: return t.__args__[0] elif issubclass(t.__origin__, Collection): # Recurse over arguments t.__args__ = tuple( [resolve_type_to_most_specific(a) for a in t.__args__] ) return t else: return t
[docs]def is_optional(t: _GenericAlias) -> bool: """Returns `True` if is a Union containing NoneType """ if hasattr(t, '__origin__'): if t.__origin__ == Union: return type(None) in t.__args__ return False
[docs]def unbind(m): try: return m.__func__ except AttributeError: return m
[docs]def bind(instance, func): # https://stackoverflow.com/a/1015405/12259362 bound_method = func.__get__(instance, instance.__class__) setattr(instance, func.__name__, bound_method) return bound_method
[docs]def separate(m): assert hasattr(m, '__self__') return m.__self__, unbind(m)