Source code for drf_braces.utils

from __future__ import absolute_import, print_function, unicode_literals
import inspect
import itertools


IGNORE_ARGS = ['self', 'cls']


[docs]def find_function_args(func): """ Get the list of parameter names which function accepts. """ try: spec = inspect.getfullargspec(func) if hasattr(inspect, 'getfullargspec') else inspect.getargspec(func) return [i for i in spec[0] if i not in IGNORE_ARGS] except TypeError: return []
[docs]def find_class_args(klass): """ Find all class arguments (parameters) which can be passed in ``__init__``. """ args = set() for i in klass.mro(): if i is object or not hasattr(i, '__init__'): continue args |= set(find_function_args(i.__init__)) return list(args)
[docs]def find_matching_class_kwargs(reference_object, klass): return { i: getattr(reference_object, i) for i in find_class_args(klass) if hasattr(reference_object, i) }
[docs]def add_base_class_to_instance(instance, base_class=None, new_name=None): """ Generic utility for adding a base class to an instance. This function returns a copy of the given instance which will then include the new base_class in its ``__mro__``. The way that is done internally is it creates a brand new class with correct bases. Then the newly created class is instantiated. Since ``__init__`` could be expensive operation in any of the base classes of the original instance mro, nto make it cheap, we temporarily switch __init__ with super simple implementation which does nothing but only instantiates class. Once instantiated, then we copy all of the instance attributes to the newly created instance. Finally, then we pop our mock ``__init__`` implementation. Args: instance (object): Instance of any object base_class (type): Any class which will be added as first class in the newly copied instance mro. Returns: Shallow copy of ``instance`` which will also inherit ``base_class``. """ # overwrite __init__ since that is mainly responsible for setting # instance state but since we explicitly copy it, we can # make __init__ a noop method def __init__(self, *args, **kwargs): pass if base_class is not None and base_class not in instance.__class__.mro(): base_classes = (base_class, instance.__class__) else: base_classes = (instance.__class__,) new_field_class = type( str(new_name or instance.__class__.__name__), base_classes, {'__init__': __init__} ) new_instance = new_field_class() new_instance.__dict__.update(instance.__dict__) # we added __init__ just for faster instantiation # since then we dont have to copy all the parameters # when creating new instance and then update its state # however after we instantiated the class, we want to # pop our silly __init__ implementation so that if somebody # wants to instantiate instance.__class__(), it will # use the original __init__ method del new_field_class.__init__ return new_instance
[docs]def initialize_class_using_reference_object(reference_object, klass, **kwargs): """ Utility function which instantiates ``klass`` by extracting ``__init__`` kwargs from ``reference_object`` attributes. Args: reference_object (object): Any object instance from which matching attributes will be used as ``klass``'s ``__init__`` kwargs. klass (type): Class which will be instantiated by using ``reference_object`` attributes. **kwargs: Any additional kwargs which will be passed during instantiation. Returns: Instantiated ``klass`` object. """ _kwargs = find_matching_class_kwargs(reference_object, klass) _kwargs.update(kwargs) return klass(**_kwargs)
[docs]def get_class_name_with_new_suffix(klass, existing_suffix, new_suffix): """ Generates new name by replacing the existing suffix with a new one. Args: klass (type): original class from which new name is generated existing_suffix (str): the suffix which needs to remain where it is new_suffix (str): the new suffix desired Example: >>> get_class_name_with_new_suffix(FooForm, 'Form', 'NewForm') 'FooNewForm' Returns: new_name (str): the name with the new suffix """ class_name = klass.__name__ if existing_suffix in class_name: prefix, suffix = class_name.rsplit(existing_suffix, 1) else: prefix, suffix = class_name, '' new_name = str('{}{}{}'.format(prefix, new_suffix, suffix)) return new_name
[docs]def get_attr_from_base_classes(bases, attrs, attr, default=None): """ The attribute is retrieved from the base classes if they are not already present on the object. Args: bases (tuple, list): The base classes for a class. attrs (dict): The attributes of the class. attr (str): Specific attribute being looked for. default (any): Whatever default value is expected if the attr is not found. Returns: attribute value as found in base classes or a default when attribute is not found and default is provided. Raises: AttributeError: When the attribute is not present anywhere in the call chain hierarchy specified through bases and the attributes of the class itself """ if attr in attrs: return attrs[attr] for base in bases: try: return getattr(base, attr) except AttributeError: continue if default is not None: return default raise AttributeError( 'None of the bases have {} attribute' ''.format(attr) )
[docs]def reduce_attr_dict_from_base_classes(bases, getter, default=None): data = (default or {}).copy() # get all field mappings from super methods for base in itertools.chain(*[reversed(i.mro()) for i in reversed(bases)]): data.update(getter(base) or {}) return data
[docs]def reduce_attr_dict_from_instance(self, getter, default=None): data = reduce_attr_dict_from_base_classes(type(self).mro(), getter, default) # this should of been picked by reduce_attr_dict_from_base_classes # however that only accounts base classes and not instance # attribute which can be modified in __init__, etc so we # explicitly account for it data.update(getter(self) or {}) return data