[docs]classDelegateAttributes:""" Mixin class to create attributes of this object by delegating them to one or more dictionaries and/or objects. Also fixes ``__dir__`` so the delegated attributes will show up in ``dir()`` and ``autocomplete``. Attribute resolution order: 1. Real attributes of this object. 2. Keys of each dictionary in ``delegate_attr_dicts`` (in order). 3. Attributes of each object in ``delegate_attr_objects`` (in order). """delegate_attr_dicts:ClassVar[list[str]]=[]""" A list of names (strings) of dictionaries which are (or will be) attributes of ``self``, whose keys should be treated as attributes of ``self``. """delegate_attr_objects:ClassVar[list[str]]=[]""" A list of names (strings) of objects which are (or will be) attributes of ``self``, whose attributes should be passed through to ``self``. """omit_delegate_attrs:ClassVar[list[str]]=[]""" A list of attribute names (strings) to *not* delegate to any other dictionary or object. """def__getattr__(self,key:str)->Any:ifkeyinself.omit_delegate_attrs:raiseAttributeError(f"'{self.__class__.__name__}' does not delegate attribute {key}")fornameinself.delegate_attr_dicts:ifkey==name:# needed to prevent infinite loops!raiseAttributeError(f"dict '{key}' has not been created in object '{self.__class__.__name__}'")try:d=getattr(self,name,None)ifdisnotNone:returnd[key]exceptKeyError:passfornameinself.delegate_attr_objects:ifkey==name:raiseAttributeError(f"object '{key}' has not been created in object '{self.__class__.__name__}'")try:obj=getattr(self,name,None)ifobjisnotNone:returngetattr(obj,key)exceptAttributeError:passraiseAttributeError(f"'{self.__class__.__name__}' object and its delegates have no attribute '{key}'")def__dir__(self)->list[str]:names=list(super().__dir__())fornameinself.delegate_attr_dicts:d=getattr(self,name,None)ifdisnotNone:names+=[kforkind.keys()ifknotinself.omit_delegate_attrs]fornameinself.delegate_attr_objects:obj=getattr(self,name,None)ifobjisnotNone:names+=[kforkindir(obj)ifknotinself.omit_delegate_attrs]returnsorted(set(names))
[docs]defstrip_attrs(obj:object,whitelist:"Sequence[str]"=())->None:""" Irreversibly remove all direct instance attributes of object, to help with disposal, breaking circular references. Args: obj: Object to be stripped. whitelist: List of names that are not stripped from the object. """try:lst=set(list(obj.__dict__.keys()))-set(whitelist)forkeyinlst:try:delobj.__dict__[key]exceptException:passexceptException:pass
[docs]defchecked_getattr(instance:Any,attribute:str,expected_type:type|tuple[type,...])->Any:""" Like ``getattr`` but raises type error if not of expected type. """attr:Any=getattr(instance,attribute)ifnotisinstance(attr,expected_type):raiseTypeError()returnattr
[docs]defgetattr_indexed(instance:Any,attribute:str)->Any:""" Similar to ``getattr`` but allows indexing the returned attribute. Returning a default value is _not_ supported. The indices are decimal digits surrounded by square brackets. Chained indexing is supported, but the string should not contain any whitespace between consecutive indices. Example: `getattr_indexed(some_object, "list_of_lists_field[1][2]")` """ifnotattribute.endswith("]"):returngetattr(instance,attribute)end:int=len(attribute)-1start:int=attribute.find("[",0,end)attr:Any=getattr(instance,attribute[0:start])start+=1while(pos:=attribute.find("][",start,end))!=-1:index=int(attribute[start:pos])attr=attr[index]start=pos+2index=int(attribute[start:end])attr=attr[index]returnattr
[docs]defchecked_getattr_indexed(instance:Any,attribute:str,expected_type:type|tuple[type,...])->Any:""" Like ``getattr_indexed`` but raises type error if not of expected type. """attr:Any=getattr_indexed(instance,attribute)ifnotisinstance(attr,expected_type):raiseTypeError()returnattr
[docs]@contextmanagerdefattribute_set_to(object_:object,attribute_name:str,new_value:Any)->"Iterator[None]":""" This context manager allows to change a given attribute of a given object to a new value, and the original value is reverted upon exit of the context manager. Args: object_: The object which attribute value is to be changed. attribute_name: The name of the attribute that is to be changed. new_value: The new value to which the attribute of the object is to be changed. """old_value=getattr(object_,attribute_name)setattr(object_,attribute_name,new_value)try:yieldfinally:setattr(object_,attribute_name,old_value)