import inspect import weakref from functools import wraps, update_wrapper from types import MethodType from .decorations import parametrizeable_decorator from .collections import ilistify, intersected_dict from .tokens import Token # for backwards compatibility class Hex(int): def __str__(self): return "%X" % self def __repr__(self): return "0x%x" % self def get_all_subclasses(cls, include_mixins=False): def is_mixin(subclass): return getattr(subclass, "_%s__is_mixin" % subclass.__name__, False) def gen(cls): for subclass in cls.__subclasses__(): if include_mixins or not is_mixin(subclass): yield subclass yield from gen(subclass) return list(gen(cls)) def stack_level_to_get_out_of_file(): frame = inspect.currentframe().f_back filename = frame.f_code.co_filename stack_levels = 1 while frame.f_code.co_filename == filename: stack_levels += 1 frame = frame.f_back return stack_levels def at_most(val, mx_val): return min(val, mx_val) def at_least(val, mn_val): return max(val, mn_val) def clamp(val, at_least, at_most): """ Clamps a value so it doesn't exceed specified limits. If one of the edges is not needed, it should be passed as None (consider using at_most / at_least functions). :param at_least: Minimum possible value. :param at_most: Maxium possible value. :return: The clamped value. """ if at_least > at_most: raise ValueError("Min value cannot be higher than max value.") if at_most is not None: val = min(at_most, val) if at_least is not None: val = max(at_least, val) return val class WeakMethodDead(Exception): pass class WeakMethodWrapper: def __init__(self, weak_method): if isinstance(weak_method, MethodType): weak_method = weakref.WeakMethod(weak_method) self.weak_method = weak_method update_wrapper(self, weak_method(), updated=()) self.__wrapped__ = weak_method def __call__(self, *args, **kwargs): method = self.weak_method() if method is None: raise WeakMethodDead return method(*args, **kwargs) @parametrizeable_decorator def kwargs_resilient(func, negligible=None): """ If function does not specify **kwargs, pass only params which it can accept :param negligible: If set, only be resilient to these specific parameters: - Other parameters will be passed normally, even if they don't appear in the signature. - If a specified parameter is not in the signature, don't pass it even if there are **kwargs. """ if isinstance(func, weakref.WeakMethod): spec = inspect.getfullargspec(inspect.unwrap(func())) func = WeakMethodWrapper(func) else: spec = inspect.getfullargspec(inspect.unwrap(func)) acceptable_args = set(spec.args or ()) if isinstance(func, MethodType): acceptable_args -= {spec.args[0]} if negligible is None: @wraps(func) def inner(*args, **kwargs): if spec.varkw is None: kwargs = intersected_dict(kwargs, acceptable_args) return func(*args, **kwargs) else: negligible = set(ilistify(negligible)) @wraps(func) def inner(*args, **kwargs): kwargs = {k: v for k, v in kwargs.items() if k in acceptable_args or k not in negligible} return func(*args, **kwargs) return inner