""" Contents of a JSON file or merged JSON files. """importjsonimportrefromhed.models.column_metadataimportColumnMetadatafromhed.errors.error_typesimportErrorContextfromhed.errorsimportErrorHandlerfromhed.errors.exceptionsimportHedFileError,HedExceptionsfromhed.models.hed_stringimportHedStringfromhed.models.column_metadataimportColumnTypefromhed.models.definition_dictimportDefinitionDict
[docs]classSidecar:""" Contents of a JSON file or JSON files. """
[docs]def__init__(self,files,name=None):""" Construct a Sidecar object representing a JSON file. Parameters: files (str or FileLike or list): A string or file-like object representing a JSON file, or a list of such. name (str or None): Optional name identifying this sidecar, generally a filename. """self.name=nameself.loaded_dict=self.load_sidecar_files(files)self._def_dict=Noneself._extract_definition_issues=[]
[docs]def__iter__(self):""" An iterator to go over the individual column metadata. Returns: iterator: An iterator over the column metadata values. """returniter(self.column_data.values())
def__getitem__(self,column_name):ifcolumn_namenotinself.loaded_dict:returnNonereturnColumnMetadata(name=column_name)@propertydefall_hed_columns(self)->list[str]:""" Return all columns that are HED compatible. Returns: list: A list of all valid HED columns by name. """possible_column_references=[column.column_nameforcolumninselfifcolumn.column_type!=ColumnType.Ignore]returnpossible_column_references@propertydefdef_dict(self)->'DefinitionDict':""" Definitions from this sidecar. Generally you should instead call get_def_dict to get the relevant definitions. Returns: DefinitionDict: The definitions for this sidecar. """returnself._def_dict@propertydefcolumn_data(self):""" Generate the ColumnMetadata for this sidecar. Returns: dict({str:ColumnMetadata}): The column metadata defined by this sidecar. """return{col_name:ColumnMetadata(name=col_name,source=self.loaded_dict)forcol_nameinself.loaded_dict}
[docs]defget_def_dict(self,hed_schema,extra_def_dicts=None)->'DefinitionDict':""" Return the definition dict for this sidecar. Parameters: hed_schema (HedSchema): Identifies tags to find definitions. extra_def_dicts (list, DefinitionDict, or None): Extra dicts to add to the list. Returns: DefinitionDict: A single definition dict representing all the data(and extra def dicts). """ifself._def_dictisNoneandhed_schema:self._def_dict=self.extract_definitions(hed_schema)def_dicts=[]ifself.def_dict:def_dicts.append(self.def_dict)ifextra_def_dicts:ifnotisinstance(extra_def_dicts,list):extra_def_dicts=[extra_def_dicts]def_dicts+=extra_def_dictsreturnDefinitionDict(def_dicts)
[docs]defsave_as_json(self,save_filename):""" Save column metadata to a JSON file. Parameters: save_filename (str): Path to save file. """withopen(save_filename,"w")asfp:json.dump(self.loaded_dict,fp,indent=4)
[docs]defget_as_json_string(self)->str:""" Return this sidecar's column metadata as a string. Returns: str: The json string representing this sidecar. """returnjson.dumps(self.loaded_dict,indent=4)
[docs]defload_sidecar_file(self,file):""" Load column metadata from a given json file. Parameters: file (str or FileLike): If a string, this is a filename. Otherwise, it will be parsed as a file-like. Raises: HedFileError: If the file was not found or could not be parsed into JSON. """ifnotfile:return{}elifisinstance(file,str):ifnotself.name:self.name=filetry:withopen(file,"r")asfp:returnself._load_json_file(fp)exceptOSErrorase:raiseHedFileError(HedExceptions.FILE_NOT_FOUND,e.strerror,file)fromeelse:returnself._load_json_file(file)
[docs]defload_sidecar_files(self,files):""" Load json from a given file or list. Parameters: files (str or FileLike or list): A string or file-like object representing a JSON file, or a list of such. Raises: HedFileError: If the file was not found or could not be parsed into JSON. """ifnotfiles:return{}ifnotisinstance(files,list):files=[files]merged_dict={}forfileinfiles:loaded_json=self.load_sidecar_file(file)merged_dict.update(loaded_json)returnmerged_dict
[docs]defvalidate(self,hed_schema,extra_def_dicts=None,name=None,error_handler=None)->list[dict]:"""Create a SidecarValidator and validate this sidecar with the schema. Parameters: hed_schema (HedSchema): Input data to be validated. extra_def_dicts (list or DefinitionDict): Extra def dicts in addition to sidecar. name (str): The name to report this sidecar as. error_handler (ErrorHandler): Error context to use. Creates a new one if None. Returns: list[dict]: A list of issues associated with each level in the HED string. """fromhed.validator.sidecar_validatorimportSidecarValidatoriferror_handlerisNone:error_handler=ErrorHandler()validator=SidecarValidator(hed_schema)issues=validator.validate(self,extra_def_dicts,name,error_handler=error_handler)returnissues
def_load_json_file(self,fp):""" Load the raw json of a given file. Parameters: fp (File-like): The JSON source stream. Raises: HedFileError: If the file cannot be parsed. """try:returnjson.load(fp)except(json.decoder.JSONDecodeError,AttributeError)ase:raiseHedFileError(HedExceptions.CANNOT_PARSE_JSON,str(e),self.name)frome
[docs]defextract_definitions(self,hed_schema,error_handler=None)->'DefinitionDict':""" Gather and validate definitions in metadata. Parameters: hed_schema (HedSchema): The schema to used to identify tags. error_handler (ErrorHandler or None): The error handler to use for context, uses a default one if None. Returns: DefinitionDict: Contains all the definitions located in the sidecar. """iferror_handlerisNone:error_handler=ErrorHandler()def_dict=DefinitionDict()self._extract_definition_issues=[]ifhed_schema:forcolumn_datainself:error_handler.push_error_context(ErrorContext.SIDECAR_COLUMN_NAME,column_data.column_name)hed_strings=column_data.get_hed_strings()forkey_name,hed_stringinhed_strings.items():hed_string_obj=HedString(hed_string,hed_schema)iflen(hed_strings)>1:error_handler.push_error_context(ErrorContext.SIDECAR_KEY_NAME,key_name)error_handler.push_error_context(ErrorContext.HED_STRING,hed_string_obj)self._extract_definition_issues+=def_dict.check_for_definitions(hed_string_obj,error_handler)error_handler.pop_error_context()iflen(hed_strings)>1:error_handler.pop_error_context()error_handler.pop_error_context()returndef_dict
[docs]defget_column_refs(self)->list[str]:""" Returns a list of column refs found in this sidecar. This does not validate Returns: list[str]: A list of unique column refs found. """found_vals=set()forcolumn_datainself:ifcolumn_data.column_type==ColumnType.Ignore:continuehed_strings=column_data.get_hed_strings()matches=hed_strings.str.findall(r"\{([a-z_\-0-9]+)\}",re.IGNORECASE)u_vals=[matchforsublistinmatchesformatchinsublist]found_vals.update(u_vals)returnlist(found_vals)