[docs]classTypedMixin:"""Based class for enforcing types on lists and sets."""def__init__(self,iterable=[],types=[]):self._types=typesifiterable:super().__init__(iterable)self._set_types()def_get_types(self):ifnothasattr(self,'_types'):self._types=[]ifself._types==[]:self._types=list(set([type(i)foriinself]))returnself._typesdef_set_types(self):ifself._types==[]:self._types=list(set([type(i)foriinself]))else:raiseException("Types have already been defined")types=property(fget=_get_types,fset=_set_types)def_check_type(self,value):if(type(value)notinself.types)and(len(self.types)>0):raiseTypeError("This list cannot accept values of type "+f"{type(value)}")
[docs]classTypedSet(TypedMixin,set):"""A set that enforces all elements have the same type."""
[docs]classTypedList(TypedMixin,list):"""A list that enforces all elements have the same type."""def__setitem__(self,ind,value):self._check_type(value)super().__setitem__(ind,value)
[docs]classSettingsAttr:r""" A custom data class that holds settings for objects. The main function of this custom class is to enforce the datatype of values that are assigned to ensure they remain consistent. For instance if ``obj.foo = "bar"``, then ``obj.foo = 456`` will fail. """def__init__(self,*args):fori,iteminenumerate(args):ifi==0:super().__setattr__('__doc__',item.__doc__)self._update(item)def__setattr__(self,attr,value):ifhasattr(self,attr):# If the the attr is already present, check its typeifgetattr(self,attr)isnotNone:# Ensure the written type is an instance of the existing onea=value.__class__.__mro__b=getattr(self,attr).__class__.__mro__c=object().__class__.__mro__check=list(set(a).intersection(set(b)).difference(set(c)))iflen(check)>0:# If they share comment parent classsuper().__setattr__(attr,value)else:# Otherwise raise an errorold=type(getattr(self,attr))new=type(value)raiseTypeError(f"Attribute \'{attr}\' can only accept "+f"values of type {old}, but the recieved"+f" value was of type {new}")else:# If the current attr is None, let anything be writtensuper().__setattr__(attr,value)else:# If there is no current attr, let anything be writtensuper().__setattr__(attr,value)def_update(self,settings,docs=False,override=False):ifsettingsisNone:returnifisinstance(settings,dict):docs=Falsefork,vinsettings.items():v=deepcopy(v)ifoverride:super().__setattr__(k,v)else:setattr(self,k,v)else:# Dataclassattrs=[iforiindir(settings)ifnoti.startswith('_')]forkinattrs:v=deepcopy(getattr(settings,k))ifoverride:super().__setattr__(k,v)else:setattr(self,k,v)ifdocs:self._getdocs(settings)@propertydef_attrs(self):a=dir(self)b=dir(list())attrs=list(set(a).difference(set(b)))attrs=[iforiinattrsifnoti.startswith('_')]attrs=sorted(attrs)returnattrsdef_deepcopy(self):returndeepcopy(self)def_getdocs(self,settings):super().__setattr__('__doc__',settings.__doc__)def__getitem__(self,key):returngetattr(self,key)def__setitem__(self,key,value):setattr(self,key,value)def__str__(self):# pragma: no coverd=PrintableDict(key="Settings",value="Values")d.update(self.__dict__)foriteminself.__dir__():ifnotitem.startswith('_'):d[item]=getattr(self,item)returnd.__str__()def__repr__(self):# pragma: no coverreturnself.__str__()