[docs]classTableError(Exception):"""Error class for table errors"""
[docs]defformat_table_lines(table_lines:List[str])->List[str]:r"""Format the lines of the markdown table In markdown tables, any whitespace or a leading/trailing `|` are ignored, so we strip these characters from the start and end of each line with a regex. """new_lines=[]forlineintable_lines:line=re.sub(r"^\s*\|?\s*","",line)line=re.sub(r"[\s\|]+\n$","\n",line)new_lines.append(line)returnnew_lines
[docs]defmarkdown_table_lines_to_dataframe(markdown_table_lines:List[str])->DataFrame:"""Convert markdown table lines to a pandas dataframe"""formatted_lines=format_table_lines(markdown_table_lines)dataframe=read_csv(StringIO("".join(formatted_lines)),sep="|",)# Strip all cells of whitespacedataframe=dataframe.rename(columns=lambdax:x.strip())dataframe=dataframe.apply(lambdacol:col.map(lambdax:x.strip()ifisinstance(x,str)elsex))dataframe=dataframe.replace("",float("nan"))ifdataframe.iloc[:,0].isnull().any():raiseTableError("Found an empty cell in the first column. The first column is ""assumed to contain the IDS paths, which is a required value ""in each row.")returndataframe.drop(0)# drop the separator row
[docs]defreadme_lines_to_mapping_table(readme_lines:List[str],mapping_table_section_heading:str="Raw to IDS Mapping")->MappingTable:"""Extract and convert mapping table lines from readme lines to a MappingTable instance. Arguments: readme_lines -- list of lines from the readme Keyword Arguments: mapping_table_section_heading -- heading of the mapping table section """table_lines=get_table_lines(readme_lines,mapping_table_section_heading)table_dataframe=markdown_table_lines_to_dataframe(table_lines)mapping_table=MappingTable(header=construct_header_from_columns(column_names=table_dataframe.columns.to_list()))mapping_table.body=construct_body_from_dataframe(keys=mapping_table.keys,dataframe=table_dataframe)returnmapping_table
[docs]defreadme_to_mapping_table(readme_path:Path,mapping_table_section_heading:str="Raw to IDS Mapping")->MappingTable:"""Convert a mapping table in a readme file to a MappingTable instance Arguments: readme_path -- path to the readme file Keyword Arguments: mapping_table_section_heading -- heading of the mapping table section """readme_lines=readme_path.read_text(encoding="utf-8").splitlines(keepends=True)returnreadme_lines_to_mapping_table(readme_lines,mapping_table_section_heading)
[docs]classUnexpectedJsonError(Exception):"""Error class for incorrect JSON formats"""
[docs]defjson_string_to_dict(json_string:str)->dict:"""Dereference a json string & ensure it is a dict"""contents=jsonref.loads(json_string)ifnotisinstance(contents,dict):raiseUnexpectedJsonError("Expected the root level to be a dictionary.")returncontents
[docs]defdataframe_to_markdown_lines(table:DataFrame)->List[str]:"""Format the mapping table string to our readme conventions. pandas.DataFrame.to_markdown() automatically pads each column to be the width of the widest cell in the column. This leads to a lot of unnecessary whitespace in many rows. To aid readability we use to_csv() with a `|` separator, and then add a single space padding to each cell manually. """table_string=table.to_csv(sep="|",index=False,quoting=QUOTE_NONE,lineterminator="\n",escapechar="\\",# pandas 3.0 started escaping the default quotechar (") too when `escapechar`# is set, even with quoting=QUOTE_NONE. We don't want that: only `|` should be# escaped. Pointing quotechar at a character that never appears in real data# keeps the quote-escaping logic from ever triggering, on any pandas version.quotechar="\x01",)# Add spaces around pipes, but preserve escaped pipes and remove trailing spacestable_string=re.sub(r"(?<!\\)\|"," | ",table_string)table_string=re.sub(r"\| \n","|\n",table_string)returntable_string.splitlines(keepends=True)
[docs]defmapping_table_to_markdown_lines(mapping_table:MappingTable)->List[str]:"""Convert the MappingTable instance to markdown table lines"""table=mapping_table.body_to_dataframe()# Only keep records that are visible=Truetable.drop(table[~table[VISIBLE]].index,inplace=True)table.drop([VISIBLE,COMMENT],axis=1,errors="ignore",inplace=True)# Format the path columntable["path"]="`"+table["path"]+"`"# Add the separator row to the start of the dataframecolumns=table.columns.to_list()separator=DataFrame([["---"]*len(columns)],columns=columns)table_with_separator=concat([separator,table])# Rename the columns to use the column_name specified in the headertable_with_separator.columns=[mapping_table.key_to_column_map[column]forcolumnincolumns]returndataframe_to_markdown_lines(table_with_separator)