# YAML Input/Output# Author: David Young, 2020"""YAML file format input/output."""fromenumimportEnumfromtypingimportAny,Callable,Dict,List,Optional,TYPE_CHECKING,Unionimportyamlfrommagmap.ioimportlibmagifTYPE_CHECKING:importpathlibdef_filter_dict(d:Dict,fn_parse_val:Callable[[Any],Any])->Dict:"""Recursively filter keys and values within nested dictionaries Args: d: Dictionary to filter. fn_parse_val: Function to apply to each value. Should call this parent function if deep recursion is desired. Returns: Filtered dictionary. """out={}forkey,valind.items():ifisinstance(val,dict):# recursively filter nested dictionariesval=fn_parse_val(val)eliflibmag.is_seq(val):# filter each val within listval=[fn_parse_val(v)forvinval]else:# filter a single valval=fn_parse_val(val)# filter keykey=fn_parse_val(key)out[key]=valreturnout
[docs]defload_yaml(path:Union[str,"pathlib.Path"],enums:Optional[Dict[str,Enum]]=None)->List[Dict]:"""Load a YAML file with support for multiple documents and Enums. Args: path: Path to YAML file. enums: Dictionary mapping Enum names to Enum classes; defaults to None. If a key or value in the YAML file matches an Enum name followed by a period, the corresponding Enum will be used. Returns: Sequence of parsed dictionaries for each document within a YAML file. Raises: FileNotFoundError: if ``path`` could not be found or loaded. """defparse_enum_val(val):# recursively parse Enum valuesifisinstance(val,dict):val=_filter_dict(val,parse_enum_val)eliflibmag.is_seq(val):val=[parse_enum_val(v)forvinval]elifisinstance(val,str):val_split=val.split(".")iflen(val_split)>1andval_split[0]inenums:# replace with the corresponding Enum classval=enums[val_split[0]][val_split[1]]returnvaltry:withopen(path)asyaml_file:# load all documents into a generatordocs=yaml.load_all(yaml_file,Loader=yaml.FullLoader)data=[]fordocindocs:ifnotdoc:# skip empty documentcontinueifenums:doc=_filter_dict(doc,parse_enum_val)data.append(doc)except(FileNotFoundError,UnicodeDecodeError)ase:raiseFileNotFoundError(e)returndata
[docs]defsave_yaml(path:Union[str,"pathlib.Path"],data:Dict,use_primitives:bool=False,convert_enums:bool=False)->Dict:"""Save a dictionary to YAML file format. Args: path: Output path. data: Dictionary to output. use_primitives: True to replace Numpy data types with Python primitives; defaults to False. convert_enums: True to convert keys and vals that are Enums to strings; defaults to False. Returns: ``data`` with any conversions. """defconvert_numpy_val(val):# recursively convert Numpy data types to primitivesifisinstance(val,dict):val=_filter_dict(val,convert_numpy_val)eliflibmag.is_seq(val):# also replaces any tuples with lists, avoiding tuple flags in# the output file for simplicityval=[convert_numpy_val(v)forvinval]else:try:val=val.item()exceptAttributeError:passreturnvaldefconvert_enum(val):# convert Enums to class.name stringsifisinstance(val,Enum):returnf"{val.__class__.__name__}.{val.name}"returnvalifuse_primitives:# replace Numpy arrays and types with Python primitivesdata=_filter_dict(data,convert_numpy_val)ifconvert_enums:data=_filter_dict(data,convert_enum)withopen(path,"w")asyaml_file:# save to YAML formatyaml.dump(data,yaml_file)print("Saved data to:",path)returndata