"""Utilities for union (sum type) disambiguation."""fromcollectionsimportOrderedDictfromfunctoolsimportreducefromoperatorimportor_fromtypingimportAny,Callable,Dict,Mapping,Optional,TypefromattrimportNOTHING,fieldsfromcattrs._compatimportget_origin
[docs]defcreate_uniq_field_dis_func(*classes:Type[Any],)->Callable[[Mapping[Any,Any]],Optional[Type[Any]]]:"""Given attr classes, generate a disambiguation function. The function is based on unique fields."""iflen(classes)<2:raiseValueError("At least two classes required.")cls_and_attrs=[(cl,set(at.nameforatinfields(get_origin(cl)orcl)))forclinclasses]iflen([attrsfor_,attrsincls_and_attrsiflen(attrs)==0])>1:raiseValueError("At least two classes have no attributes.")# TODO: Deal with a single class having no required attrs.# For each class, attempt to generate a single unique required field.uniq_attrs_dict:Dict[str,Type]=OrderedDict()cls_and_attrs.sort(key=lambdac_a:-len(c_a[1]))fallback=None# If none match, try this.fori,(cl,cl_reqs)inenumerate(cls_and_attrs):other_classes=cls_and_attrs[i+1:]ifother_classes:other_reqs=reduce(or_,(c_a[1]forc_ainother_classes))uniq=cl_reqs-other_reqsifnotuniq:m="{} has no usable unique attributes.".format(cl)raiseValueError(m)# We need a unique attribute with no default.cl_fields=fields(get_origin(cl)orcl)forattr_nameinuniq:ifgetattr(cl_fields,attr_name).defaultisNOTHING:breakelse:raiseValueError(f"{cl} has no usable non-default attributes.")uniq_attrs_dict[attr_name]=clelse:fallback=cldefdis_func(data:Mapping[Any,Any])->Optional[Type]:ifnotisinstance(data,Mapping):raiseValueError("Only input mappings are supported.")fork,vinuniq_attrs_dict.items():ifkindata:returnvreturnfallbackreturndis_func