"""Utilities to perform various checksMichel BierlaireFri Apr 5 11:09:39 2024"""fromtypingimportAbstractSet,Any,TypeVar
[docs]defcheck_consistency_of_named_dicts(named_dicts:dict[str,dict[Any,Any]],)->tuple[bool,str|None]:"""Verify that all dictionaries have the same set of keys. If not, report the inconsistencies. :param named_dicts: A dictionary where each key is a name (str) and each value is a dictionary to check. :return: True if keys are consistent across all dictionaries, False otherwise. If not consistent, returns a report detailing the inconsistencies. """# Initialize variablesdict_names=list(named_dicts.keys())keys=[set(d.keys())fordinnamed_dicts.values()]consistent=Trueinconsistencies=[]# Reference keys for comparisonreference_keys=keys[0]reference_name=dict_names[0]# Compare each set of keys with the first to check for consistencyfori,(name,key_set)inenumerate(zip(dict_names[1:],keys[1:]),start=1):ifkey_set!=reference_keys:consistent=Falsemissing_in_ref=reference_keys-key_setextra_in_ref=key_set-reference_keysifmissing_in_ref:inconsistencies.append(f'{name} missing keys compared to {reference_name}: {missing_in_ref}.')ifextra_in_ref:inconsistencies.append(f'{name} has extra keys compared to {reference_name}: {extra_in_ref}.')# Compile the report if there are inconsistenciesreport=Noneifnotconsistent:report="Inconsistencies found among dictionaries:\n"+"\n".join(inconsistencies)returnconsistent,report
[docs]defvalidate_dict_types(parameter:dict,name:str,value_type:type,key_type:type=int)->None:"""Validate the types of keys and values in a dictionary. :param parameter: The dictionary parameter to validate. :param name: The name of the parameter for error messages. :param value_type: The expected type of the dictionary values. :param key_type: The expected type of the dictionary keys. Default is int. :raises TypeError: If key or value types do not match expectations. """ifnotisinstance(parameter,dict):raiseTypeError(f"{name} must be a dict, got {type(parameter).__name__} instead.")forkey,valueinparameter.items():ifnotisinstance(key,key_type):raiseTypeError(f"Keys in {name} must be of type {key_type.__name__}, found type {type(key).__name__}.")ifnot(isinstance(value,value_type)orvalueisNone):raiseTypeError(f"Values in {name} must be of type {value_type.__name__} or None, found type {type(value).__name__}.")
T=TypeVar("T")
[docs]defassert_sets_equal(name_a:str,set_a:AbstractSet[T],name_b:str,set_b:AbstractSet[T],)->None:"""Raise an informative exception if two sets differ. :param name_a: Label for the first set (used in the error message). :param set_a: First set-like collection. :param name_b: Label for the second set (used in the error message). :param set_b: Second set-like collection. :raises ValueError: If the two sets do not contain exactly the same elements. """# Convert to real sets in case we get other Set implementationssa=set(set_a)sb=set(set_b)missing_in_b=sa-sbmissing_in_a=sb-saifmissing_in_bormissing_in_a:msg_lines=["Sets differ:"]ifmissing_in_b:msg_lines.append(f" Elements in {name_a} but not in {name_b}: "f"{[repr(x)forxinsorted(missing_in_b,key=repr)]}")ifmissing_in_a:msg_lines.append(f" Elements in {name_b} but not in {name_a}: "f"{[repr(x)forxinsorted(missing_in_a,key=repr)]}")raiseValueError("\n".join(msg_lines))